注意本篇博客多爲LeetCode中的討論題解,加上我當時的理解,放在這裏是方便本身查看答案。5部分組成:數組,鏈表,樹,動態規劃,字符串,共計170道題java
001 兩數之和node
class Solution { public int[] twoSum(int[] nums, int target) { Map<Integer,Integer> map=new HashMap<>(); for(int i = 0;i < nums.length;i++){ int complenment=target-nums[i]; if(map.containsKey(complenment)){ return new int[]{map.get(complenment),i}; } map.put(nums[i],i); } throw new IllegalArgumentException("No two sum solution"); } }
031 找出下一個數git
class Solution { public void nextPermutation(int[] nums) { //一、肯定 i 的位置 //從後向前找出開始遞減的位置 int i=nums.length-2; //由於和 i + 1 進行比較,找出 i 位置 while(i>=0&&(nums[i+1]<=nums[i])){ i--; } //二、與僅僅大於 i 的值交換 if(i>=0){ int j=nums.length-1; while(j>=0&&(nums[j]<=nums[i])){ j--; } swap(nums,i,j); } //三、逆序排列後半部分 reverse(nums,i+1); } //從 i 位置處開始交換 private void reverse(int[] nums,int start){ int i=start,j=nums.length-1; while(i<j){ swap(nums,i,j); i++; j--; } } //交換數組中的兩個元素 private void swap(int[] nums,int i,int j){ int temp=nums[i]; nums[i]=nums[j]; nums[j]=temp; } }
033 在 logn 時間內找出指定元素的位置web
class Solution { public int search(int[] nums, int target) { //判斷空數組的狀況 if(nums.length==0){ return -1; } int lo=0,hi=nums.length-1; //迭代實現 while(lo<hi){ int mid=(lo+hi)/2; if(nums[mid]==target){ return mid; } //分爲兩種狀況 //一、左端值小於中點值相似於 2,4,5,6,7,0,1 前半部分有序 if(nums[lo]<=nums[mid]){ //肯定是否前半部分有序 if(target>=nums[lo]&&target<nums[mid]){ hi=mid-1; }else{ lo=mid+1; } //二、左端值大於中點值相似於 6,7,0,1,2,4,5 後半部分有序 }else{ if(target<=nums[hi]&&target>nums[mid]){ lo=mid+1; }else{ hi=mid-1; } } } return nums[lo]==target?lo:-1; } }
034 找出目標值出如今數組中的第一個和最後一個位置;todo:用二分法實現正則表達式
//兩次掃描,O(n) 的複雜度 class Solution { public int[] searchRange(int[] nums, int target) { int[] res=new int[]{-1,-1}; if(nums.length==0){ return res; } //從前到後掃描尋找第一個目標值 for(int i=0;i<nums.length;i++){ if(nums[i]==target){ res[0]=i; //找到就退出循環 break; } } //若是不含有目標值就不用再進行掃描 if(res[0]==-1){ return res; } //從後向前掃描尋找最後一個目標值 for(int j=nums.length-1;j>=0;j--){ if(nums[j]==target){ res[1]=j; break; } } return res; } } //二分法實現,未弄懂 class Solution { // returns leftmost (or rightmost) index at which `target` should be // inserted in sorted array `nums` via binary search. private int extremeInsertionIndex(int[] nums, int target, boolean left) { int lo = 0; int hi = nums.length; while (lo < hi) { int mid = (lo + hi) / 2; if (nums[mid] > target || (left && target == nums[mid])) { hi = mid; } else { lo = mid+1; } } return lo; } public int[] searchRange(int[] nums, int target) { int[] targetRange = {-1, -1}; int leftIdx = extremeInsertionIndex(nums, target, true); // assert that `leftIdx` is within the array bounds and that `target` // is actually in `nums`. if (leftIdx == nums.length || nums[leftIdx] != target) { return targetRange; } targetRange[0] = leftIdx; targetRange[1] = extremeInsertionIndex(nums, target, false)-1; return targetRange; } }
035 將目標值插入到合適位置算法
class Solution { public int searchInsert(int[] nums, int target) { int lo=0,hi=nums.length-1; //注意有等號 while(lo<=hi){ int mid=(lo+hi)/2; if(nums[mid]==target){ return mid; } else if(target<nums[mid]){ hi=mid-1; }else{ lo=mid+1; } } return lo; } }
039 找出全部相加等於目標值的元素,元素能夠重複使用,結果數組不可重複數組
class Solution { public List<List<Integer>> combinationSum(int[] candidates, int target) { List<List<Integer>> list=new ArrayList<>(); Arrays.sort(candidates); sum(list,new ArrayList<Integer>(),candidates,target,0); return list; } private void sum(List<List<Integer>> list,List<Integer> tempList,int[] candidates,int remain,int start){ if(remain<0){ return; }else if(remain==0){ list.add(new ArrayList<>(tempList)); }else{ //循環遍歷數組中的元素,i 從 start 開始,不是從 0 開始(會出現重複數組),遞歸實現 for(int i=start;i<candidates.length;i++){ tempList.add(candidates[i]); //能夠重複利用同一個元素,傳入的開始參數爲 i sum(list,tempList,candidates,remain-candidates[i],i); // tempList.remove(candidates.length-1); tempList.remove(tempList.size()-1); } } } }
040 與 39 題的區別是元素只能被使用一次緩存
class Solution { public List<List<Integer>> combinationSum2(int[] candidates, int target) { List<List<Integer>> list=new ArrayList<>(); Arrays.sort(candidates); sum(list,new ArrayList<Integer>(),candidates,target,0); return list; } private void sum(List<List<Integer>> list,List<Integer> tempList,int[] candidates,int remain,int start){ if(remain<0){ return; }else if(remain==0){ list.add(new ArrayList<>(tempList)); }else{ //循環遍歷數組中的元素,i 從 start 開始,不是從 0 開始,遞歸實現 for(int i=start;i<candidates.length;i++){ // 跳過同一個元素屢次使用的狀況 if(i > start && candidates[i] == candidates[i-1]) continue; tempList.add(candidates[i]); //能夠重複利用同一個元素,傳入的開始參數爲 i sum(list,tempList,candidates,remain-candidates[i],i+1); // tempList.remove(candidates.length-1); tempList.remove(tempList.size()-1); } } } }
041 找出缺乏的第一個最小整數數據結構
//大於 n 的整數能夠被忽略,由於返回的值範圍是 1 到 n + 1; class Solution { public int firstMissingPositive(int[] nums) { int n=nums.length,i=0; //這兩個循環不等價 // for(int i=0;i<n;i++){ // if(nums[i]>=0&&nums[i]<n&&nums[nums[i]]!=nums[i]){ // swap(nums,i,nums[i]); // } // } while(i<n){ if(nums[i]>=0&&nums[i]<n&&nums[nums[i]]!=nums[i]){ swap(nums,i,nums[i]); }else{ i++; } } //從 1 開始,從 0 開始結果會輸出 0, int k=1; //找到第一個不按規則排列的位置 while(k<n&&nums[k]==k){ k++; } //不含元素或者找到上述的位置 if(n==0||k<n){ return k; }else{ //不懂這的含義 return nums[0]==k?k+1:k; } } private void swap(int[] nums,int i,int j){ int temp=nums[i]; nums[i]=nums[j]; nums[j]=temp; } }
042 求雨水最大面積app
//暴力法,太慢了,動態規劃和雙指針還不會 class Solution { public int trap(int[] height) { int sum=0; //注意起始和結束位置 for(int i=1;i<height.length-1;i++){ int max_left=0,max_right=0; //尋找左邊的最大值包含 i for(int j=i;j>=0;j--){ max_left=Math.max(max_left,height[j]); } //尋找右邊的最大值包含 i for(int j=i;j<height.length;j++){ max_right=Math.max(max_right,height[j]); } sum+=Math.min(max_left,max_right)-height[i]; } return sum; } }
045 跳一跳到最後
//使用貪心算法,每次找到能夠跳的最遠距離,注意只需統計跳的次數,不須要每次跳的位置 class Solution { public int jump(int[] nums) { //當前能跳到的邊界,跳到的最遠距離,步數 int end=0,maxPosition=0,steps=0; //爲何邊界在倒數第二個位置? for(int i=0;i<nums.length-1;i++){ //找能跳到最遠的,找到後暫時定下,但 i 還在向後循環 maxPosition=Math.max(maxPosition,nums[i]+i); //遇到邊界,就更新邊界 if(i==end){ end=maxPosition; steps++; } } return steps; } }
048 二維矩陣旋轉 90 度
//至關優美的解法 class Solution { public void rotate(int[][] matrix) { for(int i=0;i<matrix.length;i++){ //從 i 開始 for(int j=i;j<matrix[i].length;j++){ int temp=matrix[i][j]; matrix[i][j]=matrix[j][i]; matrix[j][i]=temp; } } for(int i=0;i<matrix.length;i++){ //從 0 開始,到二分之一長度結束 for(int j=0;j<matrix[i].length/2;j++){ int temp=matrix[i][j]; matrix[i][j]=matrix[i][matrix[0].length-1-j]; matrix[i][matrix[0].length-1-j]=temp; } } } }
053 經過動態規劃求最大的子數組的和
//加上一個大於 0 的數,數組會變的更大 class Solution { public int maxSubArray(int[] nums) { int N=nums.length; //以 nums[i] 結束的最大子數組的最大和 int[] dp=new int[N]; dp[0]=nums[0]; int ans=dp[0]; for(int i=1;i<N;i++){ dp[i]=nums[i]+(dp[i-1]>0?dp[i-1]:0); ans=Math.max(ans,dp[i]); } return ans; } }
054 順時針循環輸出矩陣
class Solution { public List<Integer> spiralOrder(int[][] matrix) { //存放結果的容器 ArrayList<Integer> ans=new ArrayList<>(); //最早進行非空判斷,不然會報空指針異常 if(matrix.length==0){ return ans; } //行數 int R=matrix.length; //列數 int C=matrix[0].length; //判斷是否已經被訪問過的,二維數組 boolean[][] seen=new boolean[R][C]; //dr dc 兩個數組組成判斷行走方向,向右 0 1,向下 1 0,向左 0 -1,向上 -1 0 int[] dr={0,1,0,-1}; int[] dc={1,0,-1,0}; //當前的行標和列標,di 爲方向表示 int r=0,c=0,di=0; //循環的次數爲矩陣中元素的個數 for(int i=0;i<R*C;i++){ ans.add(matrix[r][c]); seen[r][c]=true; //下一個點的行標和列標 int cr=r+dr[di]; int cc=c+dc[di]; //不須要轉向的狀況 if(cr>=0&&cr<R&&cc>=0&&cc<C&&!seen[cr][cc]){ r=cr; c=cc; }else{//須要轉向的狀況 //轉向分爲四種狀況經過如下代碼處理 di=(di+1)%4; r+=dr[di]; c+=dc[di]; } } return ans; } }
055 判斷是否能根據數組從第一個位置跳到最後一個位置
//不能經過,超時,經過動態規劃改進 class Solution { //判斷是否能跳到最後一個元素 public boolean canJump(int[] nums) { //第一次沒寫 return return canJumpHelper(0,nums); } private boolean canJumpHelper(int position,int[] nums){ //若是位置在最後一個元素,返回真 if(position==nums.length-1){ return true; } //能跳到的最遠位置,最後一個元素的位置是數組長度減一 int furthestPosition=Math.min(position+nums[position],nums.length-1); //注意邊界條件的等號,否則結果會出錯 for(int nextPosition=position+1;nextPosition<=furthestPosition;nextPosition++){ if(canJumpHelper(nextPosition,nums)){ return true; } } return false; } }
057 插入集合元素,可能有重合
/** * Definition for an interval. * public class Interval { * int start; * int end; * Interval() { start = 0; end = 0; } * Interval(int s, int e) { start = s; end = e; } * } */ class Solution { public List<Interval> insert(List<Interval> intervals, Interval newInterval) { //已經排好序的集合 List<Interval> result=new LinkedList<>(); // i 爲全局變量 int i=0; //一、第一種狀況,老元素的結尾小於新元素的開頭 while(i<intervals.size()&&intervals.get(i).end<newInterval.start){ // i 的自加運算不能丟掉 result.add(intervals.get(i++)); } //二、第二種狀況,新元素的結尾大於老元素的開頭,表明元素有重合 while(i<intervals.size()&&intervals.get(i).start<=newInterval.end){ //將兩個元素合併就是創造一個新的元素 newInterval=new Interval(Math.min(intervals.get(i).start,newInterval.start), Math.max(intervals.get(i).end,newInterval.end)); i++; } //將合併好的元素加入到結果集合中 result.add(newInterval); //三、第三種狀況,遍歷以後的元素將其加入到結果集合中 while(i<intervals.size()){ result.add(intervals.get(i++)); } return result; } }
059 循環輸入的二維數組
//思路:用上右下左四個變量表明四個方向,根據左右方向大小的不一樣 class Solution { public int[][] generateMatrix(int n) { //輸入的 n 表明行數和列數 int[][] result=new int[n][n]; int top=0,left=0; int down=n-1,right=n-1; //存入數組的元素 int count=1; //很精妙,怎麼想出來的呢 while(left<=right&&top<=down){ //從左向右移動 for(int i=left;i<=right;i++){ result[top][i]=count++; } top++; //從上到下移動 for(int i=top;i<=down;i++){ result[i][right]=count++; } right--; //從右到左 for(int i=right;i>=left;i--){ result[down][i]=count++; } down--; //從下到上移動 for(int i=down;i>=top;i--){ result[i][left]=count++; } left++; } return result; } }
062 探索矩陣格子中從起點到終點的路徑數目
//利用動態規劃的方法 class Solution { public int uniquePaths(int m, int n) { int[][] grid=new int[m][n]; //將第一行置爲 1 for(int i=0;i<n;i++){ grid[0][i]=1; } //將第一列置爲 1 for(int i=0;i<m;i++){ grid[i][0]=1; } //利用數學表達式 grid[i][j]=grid[i-1][j]+grid[i][j-1]; for(int i=1;i<m;i++){ for(int j=1;j<n;j++){ grid[i][j]=grid[i-1][j]+grid[i][j-1]; } } return grid[m-1][n-1]; } }
063 有障礙物的探索路徑數目,障礙用 1 表示
//利用動態規劃的方法 class Solution { public int uniquePathsWithObstacles(int[][] obstacleGrid) { int m=obstacleGrid.length; int n=obstacleGrid[0].length; //當第一個位置爲 0 時,表明沒有路徑可走 if(obstacleGrid[0][0]==1){ return 0; } //若是能夠經過,就將初始位置置爲 1 obstacleGrid[0][0]=1; //將第一行和第一列填滿做爲初始條件,若是當前數爲零而且前一個數爲一就填入 1 不然填入 0 for(int j=1;j<n;j++){ if(obstacleGrid[0][j]==0&&obstacleGrid[0][j-1]==1){ obstacleGrid[0][j]=1; }else{ obstacleGrid[0][j]=0; } } for(int i=1;i<m;i++){ //能夠經過一個 ? : 表達式進行簡化 if(obstacleGrid[i][0]==0&&obstacleGrid[i-1][0]==1){ obstacleGrid[i][0]=1; }else{ obstacleGrid[i][0]=0; } } //利用和上一題相同的遞歸式 for(int i=1;i<m;i++){ for(int j=1;j<n;j++){ if(obstacleGrid[i][j]==0){ obstacleGrid[i][j]=obstacleGrid[i-1][j]+obstacleGrid[i][j-1]; }else{ obstacleGrid[i][j]=0; } } } return obstacleGrid[m-1][n-1]; } }
064 探索路徑並求出權重和的最小值
//主要過程和以前兩道題同樣,都是經過動態規劃 class Solution { public int minPathSum(int[][] grid) { int m=grid.length; int n=grid[0].length; for(int i=0;i<m;i++){ for(int j=0;j<n;j++){ if(i==0&&j!=0){ grid[i][j]=grid[i][j]+grid[i][j-1]; }else if(j==0&&i!=0){ grid[i][j]=grid[i][j]+grid[i-1][j]; }else if(i==0&&j==0){ grid[i][j]=grid[i][j]; }else{ //出錯,不是 grid[i][j]=grid[i-1][j]+grid[i][j-1]; 而是加上上一次較小的值 grid[i][j]=Math.min(grid[i-1][j],grid[i][j-1])+grid[i][j]; } } } return grid[m-1][n-1]; } }
066 將數組做爲整數加 1
class Solution { public int[] plusOne(int[] digits) { //分爲兩種狀況,一、當各位數全爲 9 時;二、各位數不全爲 9 時 int len=digits.length-1; for(int i=len;i>=0;i--){ if(digits[i]<9){ digits[i]++; return digits; } digits[i]=0; // digits[i-1]++;不用加,進入 if 做用域自會加 } int[] newDigits=new int[len+2]; newDigits[0]=1; return newDigits; } }
073 替換 0 所在行列的數值都爲 0
//效率不高 class Solution { public void setZeroes(int[][] matrix) { //兩次遍歷,第一次將要操做的行號和列號存儲在 set 集合中,利用 contains 方法再次遍歷的時候將其改變 Set<Integer> rows=new HashSet<>(); Set<Integer> cols=new HashSet<>(); int R=matrix.length; int C=matrix[0].length; //第一次遍歷 for(int i=0;i<R;i++){ for(int j=0;j<C;j++){ if(matrix[i][j]==0){ rows.add(i); cols.add(j); } } } //第二次遍歷進行操做 for(int i=0;i<R;i++){ for(int j=0;j<C;j++){ //只要在那一行或者那一列都進行操做 if(rows.contains(i)||cols.contains(j)){ //對遍歷到的當前元素進行操做 matrix[i][j]=0; } } } } }
074 在有序的二維數組中找出目標值
class Solution { public boolean searchMatrix(int[][] matrix, int target) { //疏忽了非空判斷 if(matrix.length==0 || matrix==null || matrix[0].length==0){ return false; } //按照jianzhioffer 的方法,利用兩個指針從右上角開始探索,沒有二分查找的效率高 //行數列數寫反了 int i=0,j=matrix[0].length-1; //注意循環的終止條件,邊界條件是用行數仍是列數 while(i<matrix.length&&j>=0){ if(matrix[i][j]==target){ return true; }else if(matrix[i][j]>target){ j--; }else{ i++; } } return false; } }
075 將表明三種顏色數值在數組中進行排序
class Solution { public void sortColors(int[] nums) { //一、利用兩次遍歷的方法 int count0=0,count1=0,count2=0; for(int i=0;i<nums.length;i++){ if(nums[i]==0){ count0++; }else if(nums[i]==1){ count1++; }else{ count2++; } } for(int i=0;i<nums.length;i++){ if(i<count0){ nums[i]=0; } //連用 if 語句就不能出正確結果,改爲 else 語句就能夠了 else if(i<count0+count1){ nums[i]=1; } else{ nums[i]=2; } } //TODO: 二、利用依次遍歷的方法 } }
078 輸出一個數組的全部可能得子組合,不能有重複使用的元素
class Solution { public List<List<Integer>> subsets(int[] nums) { //正整數數組,找出全部可能的子集合,子集合中不含重複元素;特徵是遞歸和一個臨時的列表集合 //存放結果的容器 List<List<Integer>> list=new ArrayList<>(); //對數組進行排序 Arrays.sort(nums); //經過遞歸回溯的方法解決問題 backtrack(list,new ArrayList<>(),nums,0); return list; } private void backtrack(List<List<Integer>> list,List<Integer> tempList,int[] nums,int start){ list.add(new ArrayList<>(tempList)); for(int i=start;i<nums.length;i++){ tempList.add(nums[i]); backtrack(list,tempList,nums,i+1); //容器的長度計算用的是 size()方法 //該方法用於保持 t1 的原貌,能夠在下一次循環中進行添加其餘元素的操做 tempList.remove(tempList.size()-1); } } }
//遞歸軌跡 list.add for 0 t1.add list.add for 1 t2.add list.add for 2 t3.add list.add t3.rm t2.rm for 2 t2.add list.add t2.rm for 1 t1.add list.add for 2 t1.add list.add for 2 t1.add list.add t1.rm t1.rm for 2 t1.add list.add t1.add list.add t1.tm t1.rm
079 找出字母矩陣中是否包含目標單詞,從上下左右連續
class Solution { public boolean exist(char[][] board, String word) { //找出二維字母組中是否包含相應的單詞,字母不能重複利用 //主要用到了 bit mask 和 dfs 設計算法的方法; 遞歸方法 //將字符串轉換成字符數組 char[] w=word.toCharArray(); //遍歷整個二維字母矩陣 for(int y=0;y<board.length;y++){ for(int x=0;x<board[y].length;x++){ if(exist(board,y,x,w,0)){ return true; } } } return false; } //這就是深度優先搜索的過程 private boolean exist(char[][] board,int y,int x,char[] word,int i){ //一、到達字符串最後一個字母說明含有這個字符串 if(i==word.length){ return true; } //二、超出矩陣的邊界說明不含有這個字符串 if(y<0||x<0||y==board.length||x==board[y].length){ return false; } //三、出現不相等的狀況說明不含對應的字符串,當前的 exist 方法標記爲 false if(board[y][x]!=word[i]){ return false; } //利用二進制掩碼標記元素是否被訪問過,Bit Mask 的原理是什麼? board[y][x]^=256; //對四個方向進行探索,有一個方向符合條件當前 exist 方法標記爲 true,一直探索下去,知道有 true 或者 false 的返回爲止 boolean flag=exist(board,y,x+1,word,i+1) ||exist(board,y,x-1,word,i+1) ||exist(board,y+1,x,word,i+1) ||exist(board,y-1,x,word,i+1); board[y][x]^=256; return flag; } }
080 除去數組中超過兩次重複的元素後統計數組的長度
class Solution { public int removeDuplicates(int[] nums) { //有序數組,原地操做,容許兩次重複,統計出剩餘的元素個數 //有序數組,後面的數必定大於等於前面位置的數 int i=0; //從前到後依次取出數組中的元素 for(int n:nums){ //前兩個數必定可以知足要求,根據數組的有序性,若大於向前數第二個位置的數,則說明不相等,知足要求 if(i<2||n>nums[i-2]){ nums[i++]=n; } } return i; } }
081 循環的有序數組判斷目標值是否包含在數組中
class Solution { public boolean search(int[] nums, int target) { int lo=0,hi=nums.length-1; while(lo<=hi){ int mid=lo+(hi-lo)/2; if(nums[mid]==target){ return true; } //一、判斷是否處於有序的後半部分 if(nums[mid]<nums[hi]){ if(target>nums[mid]&&target<=nums[hi]){ lo=mid+1; }else{ hi=mid-1; } //二、判斷是否處於有序的前半部分 }else if(nums[mid]>nums[hi]){ if(target<nums[mid]&&target>=nums[lo]){ hi=mid-1; }else{ lo=mid+1; } //爲何要 }else{ hi--; } } return false; } }
084
//找出不規則柱狀圖中的連續最大矩形 //關鍵點:將全部的高度包括 0 高度入棧; //棧中存放的是索引; else 做用域中底邊長度的判斷 class Solution { public int largestRectangleArea(int[] heights) { //數組長度 int len=heights.length; //利用棧存放數組的索引 Stack<Integer> s=new Stack<>(); //初始化最大面積 int maxArea=0; for(int i=0;i<=len;i++){ //當前位置的高度,當在 len 位置處爲 0 int h=(i==len?0:heights[i]); //一、棧中沒有元素或者當前高度大於棧頂的索引對應的高度,就將當前高度對應的索引入棧 if(s.isEmpty()||h>=heights[s.peek()]){ //將索引加入棧中 s.push(i); //二、棧頂高度大於當前高度,將棧頂元素出棧 }else{ //拿到當前棧頂元素,也就是當前最大的那個元素 int tp=s.pop(); // i = 4 時 6 * 4 - 1 - 2 ; 再次進入 5 * 4 - 1 - 1 ,此時棧中只剩索引 1,棧爲空時說明以前是遞減的數組 maxArea=Math.max(maxArea,heights[tp]*(s.isEmpty()?i:i-1-s.peek())); i--; } } return maxArea; } }
085 ***
//在二進制矩陣中找到由 1 組成的最大矩形的面積 //利用動態規劃的方法; 或者聯繫到 084 題(這裏使用的方法) class Solution { public int maximalRectangle(char[][] matrix) { //非空判斷 if(matrix==null||matrix.length==0||matrix[0].length==0){ return 0; } //行數 int R=matrix.length; //列數 int C=matrix[0].length; //存放高度的數組 int[] h=new int[C+1]; h[C]=0; //初始化最大面積 int maxArea=0; //循環遍歷每一行 for(int r=0;r<R;r++){ Stack<Integer> s=new Stack<>(); //遍歷每一行中的元素 for(int c=0;c<=C;c++){ //構造一個和 84 題相似的數組表示柱狀圖 if(c<C){ //分兩種狀況,當前元素是 0 和當前元素是 1,題目中給的是字符串 if(matrix[r][c]=='1'){ h[c]+=1; }else{ h[c]=0; } } if(s.isEmpty()||h[c]>=h[s.peek()]){ s.push(c); }else{ //與以前的不一樣之處 while(!s.isEmpty()&&h[c]<h[s.peek()]){ int tp=s.pop(); maxArea=Math.max(maxArea,h[tp]*(s.isEmpty()?c:c-1-s.peek())); } //與以前的不一樣之處 s.push(c); } } } return maxArea; } } //動態規劃的方法未理解
088
//與歸併算法中的實現相同 //nums1 = [1,2,3,0,0,0], m = 3,注意這裏 m 的初試值爲 3 不是 6 //nums2 = [2,5,6], n = 3 class Solution { public void merge(int[] nums1, int m, int[] nums2, int n) { //定義兩個數組都是從尾部開始遍歷,組成的新數組的長度爲兩個數組長度相加,索引都是長度減一 int i=m-1,j=n-1,k=m+n-1; while(i>=0&&j>=0){ nums1[k--]=nums1[i]>nums2[j]?nums1[i--]:nums2[j--]; } while(j>=0){ nums1[k--]=nums2[j--]; } } }
090
//列出全部數組所可能包含的子數組的狀況 //所給數組中包含重複元素,結果集合中不能包含重複的集合元素 //與前一個相似的題目惟一不一樣之處在於須要跳過原數組中的重複元素 class Solution { public List<List<Integer>> subsetsWithDup(int[] nums) { List<List<Integer>> list=new ArrayList<>(); Arrays.sort(nums); backtrack(list,new ArrayList<>(),nums,0); return list; } private void backtrack(List<List<Integer>> list,List<Integer> tempList,int[] nums,int start){ list.add(new ArrayList<>(tempList)); for(int i=start;i<nums.length;i++){ //跳太重複元素 if(i>start&&nums[i]==nums[i-1]){ continue; } tempList.add(nums[i]); backtrack(list,tempList,nums,i+1); tempList.remove(tempList.size()-1); } } }
105
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ //經過給出的前序遍歷和中序遍歷結果構造二叉樹,假設樹中不存在重複元素 //pre[0] 是根節點,中序數組中 pre[0] 左邊的爲根的左子樹,右邊同理;迭代處理子數組就能獲得原始的樹 //兩個數組的左右字數對應的索引發始位置是相同的 class Solution { public TreeNode buildTree(int[] preorder, int[] inorder) { return buildTree(0,0,inorder.length-1,preorder,inorder); } //前序數組的第一個元素;中序數組的第一個元素;中序數組的最後一個元素;前序數組;中序數組 private TreeNode buildTree(int preStart,int inStart,int inEnd,int[] preorder,int[] inorder){ //傳入的兩個數組爲空, 併爲葉子結點賦值爲 null //若是 inEnd 用 inorder.length 代替會報越界異常?? if(preStart>preorder.length-1||inStart>inEnd){ return null; } //定義根節點 TreeNode root=new TreeNode(preorder[preStart]); //初始化根節點在中序遍歷數組中的索引位置 int inIndex=0; //邊界條件丟掉了等號致使 StackOverflowError 錯誤 //注意這裏的循環起點和終點是根據遞歸變化的,不是 0 和最後一位 for(int i=inStart;i<=inEnd;i++){ if(inorder[i]==root.val){ inIndex=i; } } //主要問題是肯定新的左右子樹的索引位置 //左子樹的根等於當前根的索引加一 root.left=buildTree(preStart+1,inStart,inIndex-1,preorder,inorder); //關鍵是根據中序遍歷的規律肯定第一個參數也就是根節點的在前序遍歷中的索引 //右子樹的根等於左子樹的根加上左子樹所含的節點數目 root.right=buildTree(preStart+inIndex-inStart+1,inIndex+1,inEnd,preorder,inorder); return root; } }
106
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ //根據中序和後序輸入數組構造二叉樹 class Solution { public TreeNode buildTree(int[] inorder, int[] postorder) { //後續數組的遍歷開始點與以前的前序遍歷有區別 return buildTree(postorder.length-1,0,inorder.length-1,postorder,inorder); } private TreeNode buildTree(int postStart,int inStart,int inEnd,int[] postorder,int[] inorder){ if(postStart>postorder.length-1||inStart>inEnd){ return null; } TreeNode root=new TreeNode(postorder[postStart]); int inIndex=0; for(int i=inStart;i<=inEnd;i++){ if(inorder[i]==root.val){ inIndex=i; } } //對於第一次操做 4 - (4 - 1) - 1 = 0 左子樹的根節點 //不要丟掉括號 //左子樹的根節點位置是右子樹根節點減去右子樹的節點數目 root.left=buildTree(postStart-(inEnd-inIndex)-1,inStart,inIndex-1,postorder,inorder); //右子樹的根節點是父節點索引位置減一 root.right=buildTree(postStart-1,inIndex+1,inEnd,postorder,inorder); return root; } }
118
//兩側元素都是 1 ,輸入的是二維矩陣的行數 class Solution { public List<List<Integer>> generate(int numRows) { //一個存放結果的集合 List<List<Integer>> list=new ArrayList<>(); //一、第一種基本狀況,輸入爲 0 時,結果爲空,非空判斷 if(numRows==0){ return list; } //二、第二種易得的狀況,第一行永遠爲 1,先添加一行,再將這一行的第一個元素設置爲 1 //先加一行,再取出來放入元素 1 list.add(new ArrayList<>()); list.get(0).add(1); //從第二行(也就是行索引爲 1 的位置)開始,總行數已知 //嵌套循環利用表達式將集合元素賦值 for(int r=1;r<numRows;r++){ //存放當前行數據的集合, 新建 //對每一行進行處理 List<Integer> nowRow=new ArrayList<>(); //存放前一行數據的集合,從總集合中得到 //前面已經給第一行初始化了,因此能夠直接取到第一行集合的值 List<Integer> preRow=list.get(r-1); //每一行的第一個元素老是 1 //循環取到每一行元素的時候將其第一個元素賦值爲 1 nowRow.add(1); //根據題意列表達式,每一行的元素數目等於當前行號 for(int c=1;c<r;c++){ nowRow.add(preRow.get(c)+preRow.get(c-1)); } //最後一個元素設爲 1 ,將行集合加入到總集合中 nowRow.add(1); list.add(nowRow); } return list; } }
119
//注意標號是從 0 開始,輸入行號,返回那一行的數組 class Solution { public List<Integer> getRow(int rowIndex) { List<Integer> list=new ArrayList<>(); //後面的方法能夠處理索引爲 0 的狀況 if(rowIndex<0){ return list; } //循環相加到指定索引所在的行,邊界條件很重要 for(int i=0;i<=rowIndex;i++){ //add 和 set 方法用反了 list.add(0,1); //循環到倒數第二個位置結束 for(int j=1;j<list.size()-1;j++){ //用 set 方法替換而不是用 add 向後疊加 //注意將哪兩個索引位置處的數據相加 //注意 +1 list.set(j,list.get(j)+list.get(j+1)); //不用再最後位置加 1 了 } } return list; } }
120
//從下向上依次求出相鄰的較小值疊加 //須要一個額外的數組存放數據 class Solution { public int minimumTotal(List<List<Integer>> triangle) { //由於最後用到了第長度位置處的元素 int[] result=new int[triangle.size()+1]; for(int i=triangle.size()-1;i>=0;i--){ for(int j=0;j<triangle.get(i).size();j++){ result[j]=Math.min(result[j],result[j+1])+triangle.get(i).get(j); } } return result[0]; } }
121
//返回數組中的從後到前的最大差值 class Solution { public int maxProfit(int[] prices) { int maxProfit=0; int len=prices.length; // i 到最後第二個位置爲止 for(int i=0;i<len-1;i++){ for(int j=i+1;j<len;j++){ int nowProfit=prices[j]-prices[i]; if(nowProfit>maxProfit){ maxProfit=nowProfit; } } } return maxProfit; } } //經過初始化最大值和最小值達到遍歷一遍數組就獲得結果的效果 public class Solution { public int maxProfit(int prices[]) { int minprice = Integer.MAX_VALUE; //注意設置的是最大利潤,而不是最大值 int maxprofit = 0; for (int i = 0; i < prices.length; i++) { if (prices[i] < minprice) minprice = prices[i]; else if (prices[i] - minprice > maxprofit) maxprofit = prices[i] - minprice; } return maxprofit; } }
122
//能夠進行屢次交易的求最大利潤 //將全部分段的從波峯到波谷的差值相加 //關鍵點是檢查曲線的單調性 class Solution { public int maxProfit(int[] prices) { //未進行非空判斷 if(prices==null||prices.length==0){ return 0; } int i=0; int vallay=prices[0]; int peak=prices[0]; int maxProfit=0; //倒數第二個位置爲止,在這出錯了 while(i<prices.length-1){ //尋找波谷, 找到開始遞減的位置,下一個位置就是波谷 //不是和波峯或者波谷比較,是當前元素和下一個元素比較 while(i<prices.length-1&&prices[i]>=prices[i+1]){ i++; } vallay=prices[i]; //尋找波峯,開始遞增的下一個位置爲波峯 while(i<prices.length-1&&prices[i]<=prices[i+1]){ i++; } peak=prices[i]; maxProfit+=peak-vallay; } return maxProfit; } }
123 ***
//最多進行兩次交易,求交易可以得到的最大利潤 //網格 dp 中存放的是有幾段元素時當前的最大利潤,k 表明行號,i 表明列號 //不賣,最大利潤和前一天相等,在 j 天買入並在 i 天賣出的利潤最大爲 prices[i] - prices[j] + dp[k-1, j-1] //對於本例來講畫一個 3 行 7 列的表格 class Solution { public int maxProfit(int[] prices) { if(prices.length==0){ return 0; } //至關於創建一個網格 int[][] dp=new int[3][prices.length]; for(int k=1;k<=2;k++){ for(int i=1;i<prices.length;i++){ //要放在 i 循環裏面,每一次的最小值是不一樣的 int min=prices[0]; //注意這的邊界是小於等於 i for(int j=1;j<=i;j++){ min=Math.min(min,prices[j]-dp[k-1][j-1]); } dp[k][i]=Math.max(dp[k][i-1],prices[i]-min); } } return dp[2][prices.length-1]; } }
126 ***
在這裏插入代碼片
128
//無序數組,找出最大連續串的長度 //考慮是否須要先進行排序,可能以前比遍歷過得是最大長度,也可能目前的是最大長度 class Solution { public int longestConsecutive(int[] nums) { if(nums==null||nums.length==0){ return 0; } //以前沒有先進行排序 Arrays.sort(nums); //保存最大字符串長度的變量 int longestStreak=1; //目前連續的最大長度 int currentStreak=1; //與前一個元素開始比較,所以 i 從 1 開始 for(int i=1;i<nums.length;i++){ //與前一個元素不相等的狀況下 if(nums[i]!=nums[i-1]){ if(nums[i]==nums[i-1]+1){ currentStreak++; //當遇到不連續的時候,才判斷是否替換當前的最大長度 }else{ longestStreak=Math.max(longestStreak,currentStreak); //將當前流長度重置爲 1 currentStreak=1; } } } return Math.max(longestStreak,currentStreak); } }
152
//找出無序數組中連續子數組的連乘最大值不能經過排序進行預處理了,整數數組,可正可負可爲零 //循環遍歷數組,每一步記住以前產生的最大值和最小值,最重要的是更新最大值和最小值,每一步都須要進行比較 //這裏的連續是索引位置的連續,由於題意中是子數組,上一題的連續是數組中的值的連續 class Solution { public int maxProduct(int[] nums) { if(nums==null||nums.length==0){ return 0; } int max=nums[0]; int min=nums[0]; int result=nums[0]; for(int i=1;i<nums.length;i++){ //保存 max 的值,由於求最小值的過程當中要用到未被改變的最大值 int temp=max; //由於數組中可能含有負數,因此最大的乘積可能出如今最大元素和當前元素相乘,或者最早元素和當前元素相乘 //也多是單獨的當前元素 max=Math.max(Math.max(max*nums[i],min*nums[i]),nums[i]); min=Math.min(Math.min(temp*nums[i],min*nums[i]),nums[i]); if(result<max){ result=max; } } return result; } }
153
//找到循環數組中的最小值,假設數組中沒有重複元素 //使用二分查找,只有一個元素的狀況,正好是有序數組的狀況 //與查找數組中是否含有指定元素仍是不一樣的解決方法 class Solution { public int findMin(int[] nums) { int lo=0,hi=nums.length-1; if(nums.length==1||nums[lo]<nums[hi]){ return nums[0]; } while(lo<=hi){ int mid=lo+(hi-lo)/2; //return 部分是循環的終止條件 if(nums[mid]>nums[mid+1]){ return nums[mid+1]; } if(nums[mid]<nums[mid-1]){ return nums[mid]; } //若是左半部分有序,則最小值反而在右半部分,這裏弄反了 if(nums[mid]>nums[lo]){ hi=mid-1; }else{ lo=mid+1; } } return -1; } }
在這裏插入代碼片
154
//在有序的旋轉數組中,趕上一題的不一樣是數組中可能含有重複的元素 //logn class Solution { public int findMin(int[] nums) { if(nums.length==1){ return nums[0]; } int lo=0,hi=nums.length-1; while(lo<=hi){ int mid=lo+(hi-lo)/2; //最小值落在右半部分 //與最右端的元素進行比較,而不是與最左端的元素比較 if(nums[mid]>nums[hi]){ lo=mid+1; }else if(nums[mid]<nums[hi]){ //hi 等於 mid 而不是 mid - 1,由於最小值有可能就是當前的 mid hi=mid; }else{ //去掉最後一個元素 hi--; } } //返回的是元素不是索引 return nums[lo]; } }
167
//一個升序排列的數組,輸出兩數相加等於目標數的元素索引加一,第一個索引要小於第二個索引,不能使用同一個元素超過一次 //使用 map 數據結構 class Solution { public int[] twoSum(int[] numbers, int target) { int[] result=new int[2]; if(numbers==null||numbers.length==0){ return result; } Map<Integer,Integer> map=new HashMap<>(); for(int i=0;i<numbers.length;i++){ map.put(numbers[i],i); } for(int i=0;i<numbers.length;i++){ if(map.containsKey(target-numbers[i])){ int index=map.get(target-numbers[i]); //沒有對數組中含有重複元素的狀況進行處理 if(target%2==0&&numbers[i]==target/2){ if(map.get(numbers[i])==i){ continue; } } if(index<i){ result[0]=index+1; result[1]=i+1; }else{ result[0]=i+1; result[1]=index+1; } } } return result; } }
169
//統計出數組中出現次數超過一半數組長度的元素,假設不是空數組且這樣的元素存在 //第一種方法:先進行排序,返回中間位置的元素;第二種方法:利用 map 數據結構存儲結果 //hashmap 存放的是統計出來的每個元素出現的次數,一個鍵值對就是一個 entry class Solution { public int majorityElement(int[] nums) { Map<Integer,Integer> counts=countsNums(nums); //初始化一個鍵值對爲 null Map.Entry<Integer,Integer> majorityEntry=null; //注意獲取鍵值對列表的方法 //括號不能丟 for(Map.Entry<Integer,Integer> entry:counts.entrySet()){ //若是主要的元素爲 0 或者當前鍵值對中值大於以前初始化的值,也就是出現了出現次數更多的元素 if(majorityEntry==null||entry.getValue()>majorityEntry.getValue()){ majorityEntry=entry; } } return majorityEntry.getKey(); } //構造 hashmap 數據結構的方法 private Map<Integer,Integer> countsNums(int[] nums){ Map<Integer,Integer> counts=new HashMap<>(); for(int i=0;i<nums.length;i++){ //若是鍵種不包含當前元素,將其值設置爲 1 ,包含則將其值加 1 //判斷是否包含 key 而不是直接 contains if(!counts.containsKey(nums[i])){ counts.put(nums[i],1); //加一的位置寫錯了,應該放在第一個括號後面 }else{ counts.put(nums[i],counts.get(nums[i])+1); } } return counts; } }
189
//暴力法 public class Solution { public void rotate(int[] nums, int k) { int temp, previous; for (int i = 0; i < k; i++) { previous = nums[nums.length - 1]; for (int j = 0; j < nums.length; j++) { temp = nums[j]; nums[j] = previous; previous = temp; } } } } class Solution { public void rotate(int[] nums, int k) { int len=nums.length; int[] helpArray=new int[len]; for(int i=0;i<len;i++){ //關鍵是肯定這裏的公式 helpArray[(i+k)%len]=nums[i]; } for(int i=0;i<len;i++){ nums[i]=helpArray[i]; } } }
209
//給定一個目標值和一個數組,找出數組中連續的子數組的相加和大於等於目標值,返回這樣的子數組的最小長度,若是不含有這樣的子數組,返回 0 //方法一:暴力法 //方法二:二分查找 //方法三:雙指針 class Solution { public int minSubArrayLen(int s, int[] nums) { //非空判斷 if(nums==null||nums.length==0){ return 0; } //設置兩個指針,一個用於向前加數組中的元素,一個用於向後減數組中的元素 int j=0,i=0; //用於比較判斷是否存存在符合要求的子數組,符合要求的子數組的最小長度 //整數的最大值的表示方法 int min=Integer.MAX_VALUE; //用於統計當前子數組和的變量 int sum=0; while(j<nums.length){ //注意 while 循環語句的 ++ 是放在數組索引中的 sum+=nums[j++]; while(sum>=s){ min=Math.min(min,j-i); sum-=nums[i++]; } } //不存在就返回零,存在就返回最小長度子數組的長度 return min==Integer.MAX_VALUE?0:min; } }
216
//param:k 組成指定和的元素 param:n 指定的和,return 相應的數組,組成的元素從 1 - 9 挑選,輸出的集合不能含有重複的子集合 //方法一:回溯,和以前輸出全部的子數組相似,只是判斷條件那裏作改變 class Solution { public List<List<Integer>> combinationSum3(int k, int n) { List<List<Integer>> list=new ArrayList<>(); //由於要遍歷的數組中的元素從 1 開始 backtracking(list,new ArrayList<>(),1,n,k); return list; } //須要將 k 做爲參數傳入輔助方法,否則沒法使用這個變量 private void backtracking(List<List<Integer>> list,List<Integer> tempList,int start,int n,int k){ if(tempList.size()==k&&n==0){ list.add(new ArrayList<>(tempList)); return; } for(int i=start;i<=9;i++){ tempList.add(i); backtracking(list,tempList,i+1,n-i,k); tempList.remove(tempList.size()-1); } } }
217
//判斷數組中是否含有重複的元素先排序再進行查找比較 //利用 HashSet 中的 contains 方法 class Solution { public boolean containsDuplicate(int[] nums) { Set<Integer> set=new HashSet<>(); for(int i:nums){ if(set.contains(i)){ return true; } //判斷和添加在一個做用域中進行,思考一下爲何能夠這樣 set.add(i); } return false; } }
219
//給定一個數組和一個目標值,判斷是否存在重複的元素而且,下標的差值不超過 k //比起上一題多了下標差值的限制 //判斷是否使用 foreach 看代碼中是否是要用到索引 //set 添加元素的過程當中默認判斷是否集合中已經存在鈣元素 class Solution { public boolean containsNearbyDuplicate(int[] nums, int k) { Set<Integer> set=new HashSet<>(); for(int i=0;i<nums.length;i++){ //這是一個滑動窗口, set 中只能包含 k+1 各元素,保證相同元素的差值不大於 k if(i>k){ //這個只是怎麼計算出來的呢?保證窗口中含有 k+1 個元素,那相同元素的差值就必定在 0-k 之間了 set.remove(nums[i-1-k]); } if(!set.contains(nums[i])){ return true; } } return false; } }
228
//給定一個不含有重複元素的數組,輸出這個數組包含的連續的範圍 class Solution { public List<String> summaryRanges(int[] nums) { List<String> list=new ArrayList<>(); //當數組中只有一個元素時,,直接添加第一個元素後返回 if(nums.length==1){ //加空字符串在這裏表明的是什麼意思 //不能和 return 連起來寫,單純的 add 是返回 boolean 的,加索引的話獎沒有返回值 list.add(nums[0]+""); return list; } for(int i=0;i<nums.length;i++){ //判斷是不是連續的下一個元素的條件,由於只要找到最後一個元素,因此用 while 循環,並且開始保存當前元素的值 int start=nums[i]; //判斷是否超過邊界的條件丟掉了 while(i+1<nums.length&&nums[i+1]==nums[i]+1){ i++; } int end=nums[i]; //噹噹前元素不等於以前保存的元素時,說明存在連續的數組元素 //保存初始到目前元素的輸出 //表明這是一段連續空間 if(start!=end){ list.add(start+"->"+end); //表明這是一個孤立的點 }else{ list.add(start+""); } } return list; } }
229
//尋找給定的無序數組中,出現次數超過,三分之一長度的元素,能夠有多個結果,要求時間複雜度不超過 On 空間複雜度不超過 O1,超過的意思是不包括等於的那個邊界值 //相似於摩爾投票,摩爾投票又是啥呢 //結果確定是保存在集合數據結構中 //注意一個關鍵點,重複次數超過三分之一長度的元素個數不會超過 2 個 class Solution { public List<Integer> majorityElement(int[] nums) { List<Integer> result=new ArrayList<>(); if(nums==null||nums.length==0){ return result; } //由於符合要求的元素最多有兩個,兩個計數變量設置爲 0 int num1=nums[0],count1=0; int num2=nums[0],count2=0; int len=nums.length; //第一次遍歷,若是存在符合要求的元素,將存放在 num1 和 num2 中 //計數變量 count 重複利用,因此須要再一次,遍歷數組,找到符合要求的元素 //該算法的重點全在這個邏輯,找出符合要求的元素 for(int i=0;i<len;i++){ if(nums[i]==num1){ count1++; }else if(nums[i]==num2){ count2++; }else if(count1==0){ num1=nums[i]; count1=1; //將新的多是結果的元素給 n2 }else if(count2==0){ num2=nums[i]; count2=1; //爲了再一次使用 count 給接下來的其餘元素 //將數量較少的元素統計量先減爲 0 ,用來存放下一個可能的元素 }else{ count1--; count2--; } } //將計數變量初始化爲 0 count1=0; count2=0; //再次比那裏數組找到知足要求的元素加入到結果集合中去 for(int i=0;i<len;i++){ if(nums[i]==num1){ count1++; }else if(nums[i]==num2){ count2++; } } if(count1>len/3){ result.add(num1); } if(count2>len/3){ result.add(num2); } return result; } }
238
//給定一個長度大於 1 的數組,輸出的數組對應位置的元素等於輸出除當前位置之外的元素的乘積 class Solution { public int[] productExceptSelf(int[] nums) { int len=nums.length; int[] result=new int[len]; //設置第一個元素的左邊的值和最後一個元素右邊的值都爲 1 //第一次遍歷,result 中存放的是當前元素左邊的值的乘積 //第一個元素設爲 1 result[0]=1; for(int i=1;i<len;i++){ result[i]=result[i-1]*nums[i-1]; } //right 是當前元素右邊的值的乘積 //第二次遍歷,result 中存放的就是左右兩邊乘積的積 //由於是右邊值的連乘,從後向前遍歷 int right=1; for(int i=len-1;i>=0;i--){ result[i]*=right; // right 乘的是原數組中的值,而不是 result 中的元素 right*=nums[i]; } return result; } }
268
//一個無序數組的總成元素是從 0-n 找出,中間缺乏的元素,題意假設只缺失一個元素 //方法一:先排序,在判斷,缺失的元素在第一個位置和最後一個位置單獨考慮 //方法二:利用 hashset //注意:若是缺失的是最後一個元素,由於是從零開始,不缺失的狀況下,長度應該爲最後一個元素值加一 class Solution { public int missingNumber(int[] nums) { Set<Integer> set=new HashSet<>(); //利用 foreach 便利添加全部元素到集合中 for(Integer num:nums){ set.add(num); } //本來應該的數組長度爲當前長度加一 for(int i=0;i<nums.length+1;i++){ if(!set.contains(i)){ return i; } } return -1; } }
283
//給定一個無序數組,將數組中的 0 元素移動到數組的後半部分 //先取非零的元素填入數組,再將數組中剩餘位置用 0 補全 class Solution { public void moveZeroes(int[] nums) { if(nums==null||nums.length==0){ return; } //在原數組中進行操做,不須要額外空間 //經過一個額外的變量來表示當前不爲零的元素的位置 int insertPos=0; for(int i=0;i<nums.length;i++){ if(nums[i]!=0){ //將 i 位置處不爲 0 的元素賦值到 insertPos 位置處 nums[insertPos++]=nums[i]; } } //將剩餘位置用 0 補全 while(insertPos<nums.length){ nums[insertPos++]=0; } } }
287
//給定一個無序數組 n+1 長度,由元素 1-n 組成,只有一個元素重複,輸出重複的元素 //經過 sort 或者利用 set 解決 class Solution { public int findDuplicate(int[] nums) { // hash 加快了查找的順序 Set<Integer> set=new HashSet<>(); //不是先遍歷添加元素到 set 中,而是在遍歷的過程當中添加元素,就能啓到判斷重複的做用 //返回的是元素不是索引因此用 foreach 循環更加便利 for(int num:nums){ if(set.contains(num)){ return num; } //注意添加元素相對於代碼中的位置 set.add(num); } return -1; } }
289 ***
// m*n 的二維數組,元素能夠和其周圍的八個方向上的元素進行交互,當前元素的鄰居中少於兩個元素存活時,該元素會死亡,2-3 個元素存活時能夠產生下一代,多於三個元素時,會死亡,正好等於三個元素時會死而復生 //四種狀況: 0,1 活變死 2,3 保持 3 死變活 4,5,6,7,8 活變死 //經過一個二位 bite 表明這四種狀況 //如下爲重點及難點 //得到當前元素的狀態 board[i][j] & 1 //得到下一代元素的狀態 board[i][j] >> 1 //00 dead<-dead | 01 dead<-live | 10 live<-dead | 11 live<-live class Solution { public void gameOfLife(int[][] board) { if(board==null||board.length==0){ return; } //行數 int M=board.length; //列數 int N=board[0].length; for(int m=0;m<M;m++){ for(int n=0;n<N;n++){ //統計當前元素鄰居的狀態 int lives=liveNeighbors(board,M,N,m,n); //01--->11 也就是 3 if(board[m][n]==1&&lives>=2&&lives<=3){ board[m][n]=3; } //00--->10 也就是 2 if(board[m][n]==0&&lives==3){ board[m][n]=2; } } } //產生下一代的過程,又二比特的第一位決定,0 表明不存活,1 表明存活 //每一個位置處的元素經過移位的方法產生下一代 for(int m=0;m<M;m++){ for(int n=0;n<N;n++){ //等價於 board[m][n]=board[m][n]>>1 board[m][n]>>=1; } } } //統計存活的鄰居的輔助方法 //也就是當前元素小範圍內的一個二維數組的統計 private int liveNeighbors(int[][] board,int M,int N,int m,int n){ int lives=0; //從當前元素周圍的元素開始遍歷,注意考慮四周的邊界值及表示方法 //若是輸入正好是 [0,0] 則判斷是否超出上下左右的邊界 //右邊界和下邊界分別爲長度減一,發生數組越界異常先檢查邊界的值 for(int i=Math.max(m-1,0);i<=Math.min(m+1,M-1);i++){ for(int j=Math.max(n-1,0);j<=Math.min(n+1,N-1);j++){ //這裏的當前元素是 1 ,則 board[i][j]&1 等於 1 不然爲 0 lives+=board[i][j]&1; } } //由於上述的遍歷過程包含了當前元素,在這一步須要減去當前元素的狀態 lives-=board[m][n]&1; return lives; } } //來源 : https://leetcode.com/problems/game-of-life/discuss/73223/Easiest-JAVA-solution-with-explanation
380
//每項操做 O1 的時間複雜度,能夠進行插入刪除和得到隨機值的操做,不容許含有重複的元素 //經過 ArrayList 和 HashMap 數據結構做爲底層實現,一個做爲添加元素的工具,一個做爲隨機產生相應的位置的數據 //ArrayList 的優點就是能夠經過索引獲得相應位置處的值,map 的鍵是 list 中的值,值是 list 中相應值的位置 class RandomizedSet { //存放值 List<Integer> list; //存放值和位置的鍵值對 Map<Integer,Integer> map; java.util.Random rand=new java.util.Random(); /** Initialize your data structure here. */ public RandomizedSet() { list=new ArrayList<>(); map=new HashMap<>(); } /** Inserts a value to the set. Returns true if the set did not already contain the specified element. */ public boolean insert(int val) { if(map.containsKey(val)){ return false; } //由於第一個元素的索引是 1 ,因此要先執行這一條語句 map.put(val,list.size()); list.add(val); return true; } /** Removes a value from the set. Returns true if the set contained the specified element. */ public boolean remove(int val) { //HashMap 的查找速度很快,這裏用 map 進行判斷 if(!map.containsKey(val)){ return false; } //保存要刪除的元素的索引 int index=map.get(val); //將要刪除的索引和最後一個元素交換位置 //注意邊界條件是 -1 if(index<list.size()-1){ //由於後面須要最後一個元素,暫時保存最後一個元素的值 //這是經過 list 的索引獲得其中對應的元素 int last=list.get(list.size()-1); //替換要刪除元素處的值,用 set 而不用 add //list 和 map 都要更新 //注意健值的關係 list.set(index,last); //至關於從新創建了鍵值對關係 map.put(last,index); } //map 中是經過鍵進行刪除的 map.remove(val); //將最後一個元素刪除 list.remove(list.size()-1); return true; } /** Get a random element from the set. */ public int getRandom() { //經過 list 獲得經過集合長度產生的一個隨機數 //注意得到一個隨機數的方法和邊界 return list.get(rand.nextInt(list.size())); } } /** * Your RandomizedSet object will be instantiated and called as such: * RandomizedSet obj = new RandomizedSet(); * boolean param_1 = obj.insert(val); * boolean param_2 = obj.remove(val); * int param_3 = obj.getRandom(); */
381
//能夠含有重複元素的數據結構,插入時仍然是包含返回 false 不包含返回 true //原本 map 中一個鍵是不能有兩個索引,也就是值的,經過 LinkedHashSet 將對應的值進行擴展,但存放的仍是元素在 nums 中的索引 //刪除的話只刪除其中的一個重複元素 class RandomizedCollection { List<Integer> nums; Map<Integer,Set<Integer>> locs; java.util.Random rand=new java.util.Random(); /** Initialize your data structure here. */ public RandomizedCollection() { nums=new ArrayList<>(); locs=new HashMap<>(); } /** Inserts a value to the collection. Returns true if the collection did not already contain the specified element. */ public boolean insert(int val) { //須要在這裏設置變量,否則這個值在代碼執行過程當中改變了 //返回值與插入以前的 set 中是否有該元素的這一狀態有關 boolean contain=locs.containsKey(val); if(!contain){ locs.put(val,new LinkedHashSet<>()); } //上一步的條件判斷只是加入了一個空 set,這一步向裏面添加索引 locs.get(val).add(nums.size()); nums.add(val); //不包含則返回 true,根據題意 包含就返回 false,也是趕上一題的一個不一樣之處 return !contain; } /** Removes a value from the collection. Returns true if the collection contained the specified element. */ public boolean remove(int val) { if(!locs.containsKey(val)){ return false; } //保存當前元素的位置索引,第一次出現的索引,刪除能夠指定元素刪除,可是想要獲得一個元素的位置就須要經過迭代器 int loc=locs.get(val).iterator().next(); //先從 set 中刪除該元素 locs.get(val).remove(loc); //若是要刪除的不是最後一個元素,須要交換與最後一個元素的位置,由於 linkedlist 不能指定索引位置刪除,而以前一步獲得的只是索引位置 if(loc<nums.size()-1){ //經過 nums 獲得最後的元素更爲方便 int lastone=nums.get(nums.size()-1); //將最後的元素放到要刪除元素的位置 nums.set(loc,lastone); //須要在 map 中更新 lastone 的新位置,本質上是在 set 中更新,map 只是一箇中間途徑 //先刪除,再重置 //比前一道題多了先刪除的這一步驟,刪除的是 set 集合中位於最後一位的lastone 對應的 set //也不是交換最後元素和要刪除元素的位置,而是先刪除相應的元素,再用最後的元素填補那一位置 locs.get(lastone).remove(nums.size()-1); locs.get(lastone).add(loc); } //錯誤:丟掉這條語句致使一直得不出正確結果 nums.remove(nums.size() - 1); //由於在前面代碼中刪除了 map 中 val 對應的一個 set 若是那是最後一個,那麼就能夠徹底刪除 val 這個鍵值對了 if(locs.get(val).isEmpty()){ locs.remove(val); } return true; } /** Get a random element from the collection. */ public int getRandom() { return nums.get(rand.nextInt(nums.size())); } } /** * Your RandomizedCollection object will be instantiated and called as such: * RandomizedCollection obj = new RandomizedCollection(); * boolean param_1 = obj.insert(val); * boolean param_2 = obj.remove(val); * int param_3 = obj.getRandom(); */
414
//對於一個給定的無序數組,輸出全部元素中第三大的值,若是沒有,則輸出最大的那一個 //使用 Integer 和 equals 執行速度會減慢?爲何? //使用 ?: 判別式,設置三個變量做爲最大值,次大值和第三大值 //多個 else if 的組合充分體現了程序的順序執行的特色 //若是用 int 代替可使用 Integer.MAX_VALUE 做爲起始值 class Solution { public int thirdMax(int[] nums) { //設置最大值變量 Integer max1=null; //設置次大值 Integer max2=null; //設置第三大值 Integer max3=null; //用不到索引,因此使用 foreach 遍歷,加強代碼的可讀性 for(Integer num:nums){ //跳過與最大的三個數值重複的元素 if(num.equals(max1)||num.equals(max2)||num.equals(max3)){ //不進行任何操做,結束本次循環 continue; } //當前元素比最大的值還大 //注意將 max 爲空的狀況先考慮進來,放在或操做符前面才能夠 if(max1==null||num>max1){ //從當前元素開始依次向後移動最大值 max3=max2; max2=max1; max1=num; //此處的判斷條件不用再寫小於 max1 了 }else if(max2==null||num>max2){ //最大值不懂,後面兩個值移動 max3=max2; max2=num; //寫錯一個數字標號,致使一直得不到正確結果 }else if(max3==null||num>max3){ //僅僅第三大值改變 max3=num; } } //經過查看第三大值是否被賦予新的元素來決定輸出結果 //經過前三步已經將數組中的前三大元素找出來,不存在的用 null 代替 return max3==null?max1:max3; } }
442 ***
//找出 1-n 組成的數組中重複的元素,重複的元素只出現兩次,要求時間複雜度爲 On 空間複雜度爲 O1 //遍歷的狀況下,經過一個元素出現兩次,則對應的元素負負爲正找到重複的元素 class Solution { public List<Integer> findDuplicates(int[] nums) { List<Integer> result=new ArrayList<>(); for(int i=0;i<nums.length;i++){ //經過由元素自己關聯一個新的索引 //爲何這裏須要用絕對值? //由於當確實出現重複元素的時候,nums[i] 已經變成了負值,而負值不能做爲索引 //index 與 i 既是獨立又是聯繫的索引 //i ---> nums[i] ---> index ---> nums[index] int index=Math.abs(nums[i])-1; if(nums[index]<0){ //這裏是 index+1 而不是絕對值加一 //index+1 表明的就是 nums[i] result.add(Math.abs(index+1)); } //由於若是該元素再次出現,那確定是負值了 nums[index]=-nums[index]; } return result; } }
448
//找出 1-n 組成的數組中缺失的元素,可能含有重複 //由於元素由 1-n 組成,因此元素和索引之間就能產生簡單的多層映射 class Solution { public List<Integer> findDisappearedNumbers(int[] nums) { List<Integer> result=new ArrayList<>(); for(int i=0;i<nums.length;i++){ int index=Math.abs(nums[i])-1; if(nums[index] > 0) { nums[index] = -nums[index]; } } //這裏爲何是判斷 i 位置處的元素 for(int i=0;i<nums.length;i++){ if(nums[i]>0){ result.add(i+1); } } return result; } }
457 (問題評價不高)
未作
485
//給定一個二進制數組,找到連續 1 的子數組的最大長度 //須要兩個變量,一個保存當前連續 1 的個數,一個保存最大的連續個數 //注意開始未進行初始化 class Solution { public int findMaxConsecutiveOnes(int[] nums) { //最大的連續個數 int result=0; //當前的連續個數 int count=0; for(int num:nums){ if(num==1){ count++; result=Math.max(count,result); //丟掉了 else 重置計數變量 }else{ count=0; } } return result; } }
495
//數組表明每次攻擊的時間點,形成持續性傷害,每次攻擊持續指定個時間單位,輸出一共持續受到傷害的時間總長度 //判斷的時候分兩種狀況,下一次攻擊來臨時,上一次的持續性傷害沒有結束;或者下一次攻擊來臨以前上一次的持續性傷害已經結束 class Solution { public int findPoisonedDuration(int[] timeSeries, int duration) { //非空判斷 //不能少 if(timeSeries==null||timeSeries.length==0||duration==0){ return 0; } //存放結果的變量 int result=0; //對於第一次攻擊的開始時間和持續結束時間初始化 int start=timeSeries[0],end=timeSeries[0]+duration; for(int i=1;i<timeSeries.length;i++){ if(timeSeries[i]>end){ //將持續時間添加到結果變量中 result+=end-start; //更新開始的時間點 //與下面 end 語句位置放反了致使出錯 start=timeSeries[i]; } //若是下一次擊打時仍有持續傷害則不須要更新 start 只需更新 end //新一次攻擊持續時間結束的時間點 end=timeSeries[i]+duration; } //添加最後一次攻擊持續的時間 //或者只有一次攻擊,程序根本沒有執行到循環中 result+=end-start; return result; } }
002
//給定兩個正數的非空鏈表經過鏈表進行多位數的加法運算,其中的元素反向存儲在鏈表中 //由於每一位的數字是 0 到 9 之間,因此相加後可能溢出,包括進位的最大可能總和爲 9+9+1=19,因此表示進位的變量不是 0 就是 1 //明確鏈表的頭是哪一個 /** * Definition for singly-linked list. 定義的節點類 * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public ListNode addTwoNumbers(ListNode l1, ListNode l2) { //初始化臨時的頭結點爲 0 ListNode dummyHead=new ListNode(0); //進位標誌符初始化爲 0 int carry=0; //臨時變量 ListNode p=l1,q=l2,curr=dummyHead; //或條件判斷,只要有一個鏈表沒有遍歷完就繼續 while(p!=null||q!=null){ //若是一個鏈表比較短,先到達尾節點,根據條件判斷加上的值 //每一位的數字 int x=(p!=null)?p.val:0; int y=(q!=null)?q.val:0; //計算當前位置的和 int sum=x+y+carry; carry=sum/10; //和鏈表的新節點賦值 curr.next=new ListNode(sum%10); //下一個結點指向當前節點 curr=curr.next; //不爲空的狀況下才繼續向下移動 if(p!=null){ p=p.next; } if(q!=null){ q=q.next; } } //最高位須要向前進位 if(carry>0){ curr.next=new ListNode(carry); //有沒有都同樣,只會下降效率 curr=curr.next; } return dummyHead.next; } }
019
//從鏈表尾部刪除第 n 個節點,並返回頭結點 //遍歷兩次鏈表 //找到要刪除節點的前一個節點,進行跳過下一個節點的操做 /** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public ListNode removeNthFromEnd(ListNode head, int n) { //建立一個新鏈表用於返回 ListNode dummy=new ListNode(0); //暫時將老鏈表拼接到結果鏈表之上 //否則會報空指針異常 dummy.next=head; //老鏈表長度 int length=0; //統計哪一個鏈表的長度就指向哪一個鏈表的頭 ListNode first=head; //統計鏈表長度 while(first!=null){ length++; first=first.next; } length-=n; //開始新鏈表的遍歷 //沒有指向就會報空指針異常 first=dummy; while(length>0){ length--; first=first.next; } //跳過要刪除的節點 first.next=first.next.next; return dummy.next; } }
021
//合併兩個有序鏈表,要求新鏈表仍然有序,超時 /** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public ListNode mergeTwoLists(ListNode l1, ListNode l2) { //特殊狀況的判斷 //全爲空 if(l1==null&&l2==null){ return null; } //其中一個爲空就返回另外一個 if(l1==null){ return l2; } if(l2==null){ return l1; } ListNode dummty=new ListNode(0); ListNode prev=dummty; //正常的判斷邏輯,比較兩個相應節點的大小,先添加較小的節點值 //l1 l2 均不爲零的狀況 while(l1!=null&&l2!=null){ if(l1.val<=l2.val){ //相應的節點接到 prev 鏈表上 //相應的老鏈表向後移動一位 prev.next=l1; l1=l1.next; }else{ prev.next=l2; l2=l2.next; } //超時的緣由是丟掉這一句,一直在循環 prev = prev.next; } //若是又剩下的節點沒有添加,所有添加到後面 //用 if 語句而不是 while if(l1!=null){ //至關於把 l1 後面接的一串都添加進去了 prev.next=l1; } if(l2!=null){ prev.next=l2; } return dummty.next; } } //遞歸方法,未理解 class Solution { public ListNode mergeTwoLists(ListNode l1, ListNode l2) { if(l1 == null) return l2; if(l2 == null) return l1; if(l1.val < l2.val){ l1.next = mergeTwoLists(l1.next, l2); return l1; } else{ l2.next = mergeTwoLists(l1, l2.next); return l2; } } }
023
//多路鏈表的歸併,上一道題的擴展形式 //經過優先隊列的數據結構,主要利用了優先隊列的輪訓查找最小值方法 /** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public ListNode mergeKLists(ListNode[] lists) { //非空判斷 if(lists==null||lists.length==0){ return null; } //經過內部類的方式建立優先隊列 //建立具備初始容量,而且根據指定的比較器進行比較的隊列 PriorityQueue<ListNode> queue=new PriorityQueue<ListNode>(lists.length,new Comparator<ListNode>(){ //重寫比較方法,比較的是兩個鏈表節點中的值 //比較器的內部類 public int compare(ListNode l1,ListNode l2){ if(l1.val<l2.val){ return -1; }else if(l1.val==l2.val){ return 0; }else{ return 1; } } }); ListNode dummty=new ListNode(0); ListNode tail=dummty; //將集合中的頭結點添加到隊列中 for(ListNode node:lists){ if(node!=null){ queue.add(node); } } //在隊列中利用輪訓將多個鏈表拼接成一個 while(!queue.isEmpty()){ //將比較小的頭結點賦值到目標鏈表中 tail.next=queue.poll(); //兩個節點鏈接起來 //tail 指針移動到下一個 tail=tail.next; //tail 的下一個結點就是當前三個中的最小值的下一個結點 //指針怎麼移動到多個鏈表的下一個結點是關鍵 if(tail.next!=null){ //將以前移動了的下一個結點添加到隊列 queue.add(tail.next); } } return dummty.next; } }
024
//交換相鄰兩個節點,只能對節點進行總體操做,而不能改變節點的值 /** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public ListNode swapPairs(ListNode head) { //新鏈表頭結點以前的元素 ListNode dummy=new ListNode(0); dummy.next=head; //一個指向頭結點的指針 ListNode curr=dummy; //當前節點的下一個結點和下下一個節點都不爲空時,說明能夠進行交換 while(curr.next!=null&&curr.next.next!=null){ //須要兩個指針保存下面兩個節點 ListNode first=curr.next; ListNode second=curr.next.next; //1 ---> 3 //.next賦值就是鏈表箭頭的指向 first.next=second.next; //curr ---> 2 curr.next=second; //2 ---> 1 curr.next.next=first; //curr 向後挪兩個位置 curr=curr.next.next; } return dummy.next; } }
025
//將鏈表中的節點按輸入的單位長度進行倒置,若是剩餘節點不足這個數組,則不進行操做 /** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } *---------------------------- *prev *tail head *dummy 1 2 3 4 5 *---------------------------- *prev head tail *dummy 1 2 3 4 5 * cur *---------------------------- * 每次讓prev.next的元素插入到當前tail以後,這樣tail不斷前移,被挪動的元素頭插入tail以後的鏈表 *prev tail head *dummy 2 3 1 4 5 * cur *---------------------------- *prev tail head *dummy 3 2 1 4 5 * cur *---------------------------- * prev * tail * head *dummy 3 2 1 4 5 *---------------------------- * prev head tail *dummy 3 2 1 4 5 null *---------------------------- */ class Solution { public ListNode reverseKGroup(ListNode head, int k) { //什麼狀況下不進行改變直接輸出 if(head==null||head.next==null||k<2){ return head; } ListNode dummy=new ListNode(0); dummy.next=head; //三個指針,前,尾,臨時 //前尾指針用於找到相應的要反轉的部分 //當前指針在以後的致使過程當中使用,開始並不使用 ListNode tail=dummy,prev=dummy,curr; //保存 k 的變量,由於每一次倒置都須要固定的 k int count; //注意用無限循環的方法,和 break 語句的使用 while(true){ //以 k 節點爲邊界進行遍歷 count=k; //先向後遍歷 k 個節點 //先肯定第一段反轉的尾節點 while(count>0&&tail!=null){ count--; tail=tail.next; } //tail 到達尾節點,退出外層循環 if(tail==null){ break; } //每一段都須要新的 head 來進行反轉 head=prev.next; //用做截取的頭尾節點沒有相遇就可繼續反轉 while(prev.next!=tail){ //每一步循環都是將頭結點移動到尾節點的下一個 //將要吧頭結點移動走 curr=prev.next; //找到頭結點的下一個進行與第零個節點鏈接 prev.next=curr.next; //將頭節點移動到原來尾節點的位置 curr.next=tail.next; //將尾節點向前移動一個 tail.next=curr; } //反轉以後 head 節點已經移動到了一段的最後一個位置 tail=head; prev=head; } return dummy.next; } }
061
//將由正整數組成的鏈表循環向後移動 k 個位置 //找出移動先後對應節點的位置關係式 /** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public ListNode rotateRight(ListNode head, int k) { //當鏈表爲空或者只含有一個元素的時候,直接返回頭結點 if(head==null||head.next==null){ return head; } //新建一個要返回的鏈表 ListNode dummy=new ListNode(0); //下一個結點指向老鏈表的頭結點 dummy.next=head; //兩個特殊的節點,一個是老鏈表的尾節點,一個是新鏈表的尾節點 ListNode oldTail=dummy,newTail=dummy; //統計鏈表的總長度 int length=0; //是下一個結點不爲空,就繼續循環 //報空指針異常通常都是邊界出了問題 while(oldTail.next!=null){ length++; oldTail=oldTail.next; } //找到新鏈表的尾節點 int index=length-k%length; while(index>0){ newTail=newTail.next; index--; } //改變頭結點和新老尾節點的指向 oldTail.next=dummy.next; dummy.next=newTail.next; newTail.next=null; return dummy.next; } }
082
//給定一個有序的鏈表,刪除重複的節點再返回,刪除後的鏈表 /** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public ListNode deleteDuplicates(ListNode head) { if(head==null||head.next==null){ return head; } ListNode dummy=new ListNode(0); dummy.next=head; //當前節點在頭結點的下一個結點 ListNode prev=dummy,curr=dummy.next; //注意循環結束的邊界條件 while(curr!=null){ //找到相同元素段的尾節點 //何時用 while 何時用 if while(curr.next!=null&&curr.val==curr.next.val){ curr=curr.next; } //經過判斷前節點的下一個結點是不是當前節點查看這個節點元素是否惟一 if(prev.next==curr){ //這個節點惟一 prev=prev.next; }else{ //須要刪除全部重複的節點,一個也不剩 prev.next=curr.next; } //無論有無重複的元素,curr 指針都須要繼續向前移動,進入下次一循環 curr=curr.next; } return dummy.next; } }
083
//刪除重複的節點,而且保留一個重複的節點再鏈表中,上一題中是將重複的借點所有刪除 //直接按照邏輯寫代碼,若是重複就跳過 /** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public ListNode deleteDuplicates(ListNode head) { //不知道這樣作的必要性在哪裏 ListNode dummy=new ListNode(0); dummy.next=head; //當前節點從頭結點做爲起點,否則會出現異常 ListNode curr=dummy.next; //須要當前節點和下一個結點均不爲空時才進行循環 //由於在循環語句中用到了 next 的值,因此須要判斷是否下一個結點爲空 while(curr!=null&&curr.next!=null){ if(curr.val==curr.next.val){ //與下一個結點同樣,就跳過當前節點 curr.next=curr.next.next; }else{ //移動當前節點的指針 //與 if 語句造成互斥的關係 curr=curr.next; } } return dummy.next; } }
086
//將鏈表中的節點進行分區,以輸入的 x 爲分區邊界點,兩個半區內各節點的相對順序不變 //在添加點處打破改變原鏈表的結構 //使用兩個指針跟蹤建立的兩個鏈表,最後將兩個鏈表組合咱們須要的結果 //使用虛擬節點進行初始化鏈表能夠減小咱們的條件檢測次數,而且使得實現更容易,在合併兩個鏈表時,只須要兩個指針向前移動就好 /** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public ListNode partition(ListNode head, int x) { //虛擬頭結點 //虛擬節點將一直保留在開始位置,方便直接進行操做 ListNode before_dummy=new ListNode(0); ListNode after_dummy=new ListNode(0); //分別指向兩個新鏈表的指針 ListNode before=before_dummy; ListNode after=after_dummy; //原始鏈表經過 head 指針進行遍歷 while(head!=null){ //遍歷到的節點小於 x 就移動到前半部分的鏈表,大於就移動到後半部分的鏈表 //那半部分的鏈表增長了,鏈表指針就向前移動一個 if(head.val<x){ before.next=head; before=before.next; }else{ after.next=head; after=after.next; } head=head.next; } //尾節點的後面接空節點 after.next=null; //前半部分的尾節點接後半部分虛擬節點的下一位 //在這裏體現出虛擬節點的優越性 before.next=after_dummy.next; return before_dummy.next; } }
092 ***
//難點,單向鏈表中沒有向後的指針和索引 //經過遞歸中的回溯過程模擬指針向後的移動,若是子鏈表長度是奇數,左右指針將會重合,偶數,左右指針則會造成交叉 /** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { //使用全局布爾標誌來中止指針交叉後的交換 private boolean stop; //用於充當鏈表左端點的左指針 private ListNode left; public ListNode reverseBetween(ListNode head, int m, int n) { this.left=head; this.stop=false; this.recurseAndReverse(head,m,n); return head; } //遞歸和反轉的方法 private void recurseAndReverse(ListNode right,int m,int n){ //遞歸的基準條件,右端位置是 1 時,退出當前遞歸 if(n==1){ return; } //移動右指針直到 n==1 right=right.next; if(m>1){ //由於是全局變量,不是從參數中傳進來的變量,須要加上 this 關鍵詞 this.left=this.left.next; } this.recurseAndReverse(right,m-1,n-1); if(this.left==right||right.next==this.left){ this.stop=true; } //若是沒有知足結束遞歸的條件,須要交換兩個指針指向的節點的位置 //交換的是節點中保村的值,而不是交換節點 if(!this.stop){ //臨時變量存放左端點的值 int temp=this.left.val; this.left.val=right.val; right.val=temp; //交換過節點以後,左節點向右移動一個位置,右節點向左回溯一個位置 this.left=this.left.next; } } }
109 ***
//給定一個有序的單向鏈表,將其轉換成一個平衡二叉樹,平衡二叉樹的定義是一個節點的兩個子樹高度相差不超過 1 //遞歸方法 /** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { //創建樹的遞歸方法 public TreeNode sortedListToBST(ListNode head) { if(head==null){ return null; } //找到鏈表的中間元素 ListNode mid=this.findMiddleElement(head); //之間位置的節點成爲二叉搜索樹的根節點 TreeNode node=new TreeNode(mid.val); //當子鏈表中只有一個節點時,結束遞歸 if(head==mid){ return node; } //使用左半部分和右半部分遞歸的造成二叉搜索樹 node.left=this.sortedListToBST(head); node.right=this.sortedListToBST(mid.next); return node; } //遞歸方法 private ListNode findMiddleElement(ListNode head){ //用於斷開左端和中間節點的指針 ListNode prev=null; //兩個步進分別爲 1 和 2 的指針 //目的是找出中間位置節點 ListNode slow=head; ListNode fast=head; //當步進爲 2 的指針到達尾節點的時候,步進爲 1 的指針正好到達中間位置,也就是根節點 while(fast!=null&&fast.next!=null){ prev=slow; slow=slow.next; fast=fast.next.next; } //將前半部分與中間節點斷開, prev 表明中間節點的前一個節點 if(prev!=null){ prev.next=null; } return slow; } }
138
//生成一個特殊的鏈表,每一個節點除了 next 指針以外還含有隨機指針,最終目的是對這種特殊的鏈表進行深拷貝 //普通方法和使用 map 數據結構進行優化 //就是可以經過節點訪問到鏈表中的值下一個結點和下一個隨機節點 /* // Definition for a Node. class Node { public int val; public Node next; public Node random; public Node() {} public Node(int _val,Node _next,Node _random) { val = _val; next = _next; random = _random; } }; */ class Solution { public Node copyRandomList(Node head) { //鏈表的非空判斷,否則在進行 next 操做時會出現空指針異常 if(head==null){ return null; } //這裏的 node 節點至關於以前經常使用的 curr 節點的表示方法 Map<Node,Node> map=new HashMap<>(); //添加節點到節點的映射 Node node=head; while(node!=null){ //至關於只是進行的賦值操做 map.put(node,new Node(node.val,node.next,node.random)); node=node.next; } //指定下一個節點和隨機節點的值 //進行鏈表之間的鏈接操做 node=head; //這裏不能使用 head 遍歷,由於 head 在這充當的是虛擬節點的做用,用來返回結果鏈表 while(node!=null){ //先從 map 中取得節點 map.get(node).next=map.get(node.next); map.get(node).random=map.get(node.random); node=node.next; } return map.get(head); } }
141
//在鏈表中產生環,根據指定的 pos 的位置產生從尾節點指向那個位置的環,pos = -1 表示不產生環 //注意目的是判斷鏈表中是否含有環 //兩種方法:快慢指針和 set 數據結構 /** * Definition for singly-linked list. * class ListNode { * int val; * ListNode next; * ListNode(int x) { * val = x; * next = null; * } * } */ public class Solution { public boolean hasCycle(ListNode head) { Set<ListNode> set=new HashSet<>(); while(head!=null){ if(set.contains(head)){ return true; }else{ set.add(head); } head=head.next; } return false; } } //第二種方法,快慢指針 public class Solution { public boolean hasCycle(ListNode head) { ListNode slow=head; ListNode fast=head; while(fast!=null&&fast.next!=null){ slow=slow.next; fast=fast.next.next; if(slow==fast){ return true; } } return false; } }
142
//返回含有環的鏈表環開始的相對節點的第幾個位置 //快慢指針 /** * Definition for singly-linked list. * class ListNode { * int val; * ListNode next; * ListNode(int x) { * val = x; * next = null; * } * } */ public class Solution { public ListNode detectCycle(ListNode head) { ListNode slow=head; ListNode fast=head; while(fast!=null&&fast.next!=null){ //慢指針先進行移動,而且是快指針追遇上慢指針才行,反過來不能夠 slow=slow.next; fast=fast.next.next; //快指針走過的距離是慢指針的二倍,所以新指針以 1 的步進向前移動,與滿指針再次相遇的時候就是進入環的位置 //環中須要的步數等於走到慢指針位置須要的步數 //慢指針再走幾步走滿新的一圈,考慮重合的步數,至關於從起點開始再走幾步走到環開始的位置 if(fast==slow){ //經過一個新的指針 ListNode temp=head; while(temp!=slow){ slow=slow.next; temp=temp.next; } return slow; } } return null; } }
143 ***
//重點是看出將鏈表從中間截取,把後半部分的節點按照要求插入到前半部分 //因此問題的解決能夠分爲如下三步 //找到中間的節點,將後半部分逆序,將兩段鏈表鏈接成一段鏈表 //head 節點默認指向的是第一個節點,不是第一個節點前的虛擬節點 /** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public void reorderList(ListNode head) { //非空或者一個節點判斷,一個節點的時候必須也得單獨考慮 if(head==null||head.next==null){ return; } //經過快慢指針的方法取得中間節點的位置 ListNode slow=head; ListNode fast=head; //循環條件是下一個結點和下下一個節點不是空,而不是當前和下一個結點 //兩個條件的順序不能變 //這個判斷條件與後面一體的判斷條件當節點個數爲奇數時沒有區別,節點個數爲偶數時,這個 slow 落在前半區最後一個節點,下一題落在後半區的第一個節點 while(fast.next!=null&&fast.next.next!=null){ slow=slow.next; fast=fast.next.next; } //中間節點的位置就是如今 slow 指針指向的位置 ListNode prevMidd=slow; ListNode prevCurr=slow.next; //依次將中間節點後面的節點兩兩交換位置 //由於交換位置的時候用到了 prevCurr 節點的下一個結點,因此判斷循環的結束條件須要是該節點的下一個結點 //下面代碼中用到了三個指針,要注意區分,交換的是四個節點中間的兩個節點 while(prevCurr.next!=null){ //交換時用到的暫時保存的節點 //操做時是按照上一條語句的右邊是下一條語句的左邊的順序進行交換 ListNode nextTemp=prevCurr.next; prevCurr.next=nextTemp.next; nextTemp.next=prevMidd.next; prevMidd.next=nextTemp; //交換完以後已經至關於當前指針向後移動了一個位置,因此不須要在經過代碼 next 來移動當前指針 } //依次將兩段鏈表組合成符合要求的一段鏈表 //慢指針從第一個及節點開始,快指針從中間節點的下一個結點開始,分別是兩段鏈表的第一個節點 slow=head; //這裏與前面快慢指針的循環終止條件相對應 fast=prevMidd.next; //前一段鏈表的長度等於後一段鏈表的長度或者比其長度多一個 //沒有返回值不須要使用虛擬指針,否則反而更加麻煩 while(slow!=prevMidd){ //從中間節點的斷開開始執行 //從斷點處向後拿出一個節點插入到前面,也沒有斷開鏈表 //每一步的順序不能有變化 prevMidd.next=fast.next; fast.next=slow.next; slow.next=fast; //進行指針的向前移動時須要注意 //原來的 slow 指針的下一個節點,是如今 fast 指針的下一個結點 slow=fast.next; //原來的 fast 指針的下一個結點,是如今中間節點指針的下一個結點 fast=prevMidd.next; } } }
147
//使用插入排序的方法對一個鏈表進行排序 /** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public ListNode insertionSortList(ListNode head) { if(head==null||head.next==null){ return head; } ListNode dummy=new ListNode(0); ListNode curr=head; while(curr!=null){ //每一次從虛擬節點從新開始進行比較 //至關於每次將指針初始化到頭結點的位置 ListNode prev=dummy; //dummy.null 等於空,第一次不進入循環,只將鏈表中的第一個節點插入到虛擬節點和空節點之間 while(prev.next!=null&&prev.next.val<curr.val){ prev=prev.next; } //通過上面的語句,指針停留在前面均小於當前指針的位置處 //在 prev 與 after 之間插入 curr //由於 curr.next 遭到了破壞,須要暫時保存下一個節點的信息 ListNode next=curr.next; curr.next=prev.next; prev.next=curr; curr=next; } return dummy.next; } }
148
//在 O(nlogn) 時間複雜度以內對鏈表進行排序 //歸併排序的方法的使用,與遞歸的結合 //爲何從中間位置的前一個位置處斷開,由於若是是含有偶數個節點,中間節點的前一個位置正好將長度一分爲二,奇數則前半部分比後半部分少一個節點 /** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public ListNode sortList(ListNode head) { //遞歸的基準條件 if(head==null||head.next==null){ return head; } //經過快慢指針找到中間節點 //想要徹底分割成兩個鏈表就須要在左鏈表的結束位置加上 null 節點 ListNode slow=head,fast=head,prev=null; //prev 始終是 slow 上一個節點 //這裏的邊界條件影響着是否含有空指針異常 //若是用前一體的條件,須要保存的就是 slow 後一個節點的狀況 while(fast!=null&&fast.next!=null){ prev=slow; slow=slow.next; fast=fast.next.next; } //將兩段鏈表斷開,從 slow 指針的前一個位置 prev.next=null; //對每一個半區進行排序 ListNode left=sortList(head); //至關於傳入了第二段鏈表的 head ListNode right=sortList(slow); //歸併操做 return merge(left,right); } private ListNode merge(ListNode left,ListNode right){ //使用虛擬指針進行歸併 ListNode dummy=new ListNode(0); ListNode curr=dummy; //與以前兩條鏈表的歸併排序徹底同樣 //不是判斷 curr 而是判斷左右鏈表的狀況 while(left!=null&&right!=null){ if(left.val<right.val){ curr.next=left; left=left.next; }else{ curr.next=right; right=right.next; } curr=curr.next; } if(left!=null){ curr.next=left; } if(right!=null){ curr.next=right; } return dummy.next; } }
160
//找出兩條匯合鏈表匯合處節點的值 //非空檢查也就是邊界檢查,考慮比較特殊的輸入值的狀況 //爲何不須要預處理兩個鏈表的差別?由於咱們只要保證兩個指針同時到達交叉節點處就行 /** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { * val = x; * next = null; * } * } */ public class Solution { public ListNode getIntersectionNode(ListNode headA, ListNode headB) { //邊界檢查 if(headA==null||headB==null){ return null; } ListNode a=headA; ListNode b=headB; while(a!=b){ //當一種一個指針到達一個鏈表的結尾時,讓其指向另外一個鏈表的開始,若是有交叉,呢麼必然會在交叉點處匯合 //如下式子能夠用多想表達式直接替換 //a = a == null? headB : a.next; //b = b == null? headA : b.next; if(a!=null){ a=a.next; }else{ a=headB; } if(b!=null){ b=b.next; }else{ b=headA; } } return a; } }
203
//從鏈表中刪除含有指定值的節點,該節點可能不僅有一個 //方法:設置兩個指針,一個在前,一個在後,若是須要刪除,就用前面的指針的下一個結點的操做對後指針進行跳過也就是刪除的操做 //但凡是須要返回鏈表的,都要考慮下是否須要虛擬節點 /** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public ListNode removeElements(ListNode head, int val) { //由於要用到頭結點的前一個節點,使用虛擬節點較爲方便 ListNode dummy=new ListNode(0); dummy.next=head; ListNode prev=dummy,curr=head; while(curr!=null){ if(curr.val==val){ prev.next=curr.next; }else{ //不進行任何操做,只在移動 curr 前,將指針向後移動一個位置 prev=prev.next; } curr=curr.next; } return dummy.next; } }
206
//將整個鏈表進行反轉 //主要分爲兩種方法,分別是遞歸和迭代;遞歸比迭代可能更容易理解,可是空間複雜度會更高 /** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public ListNode reverseList(ListNode head) { //調轉每一個指針箭頭的指向 //一個指針指向尾節點,一個從頭結點開始向後移動 ListNode tail=null; ListNode curr=head; //至關於循環進行的操做 while(curr!=null){ //暫時保存當前節點的下一個節點 ListNode nextTemp=curr.next; //頭結點的下一個結點指向當前的尾節點,第一次進行操做時就是尾節點 curr.next=tail; //curr 在上一條命令後變成新的尾節點 tail=curr; curr=nextTemp; } return tail; } } //遞歸的方法 public ListNode reverseList(ListNode head) { if (head == null || head.next == null) return head; ListNode p = reverseList(head.next); head.next.next = head; head.next = null; return p; }
234
//肯定鏈表是不是迴文鏈表 /** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public boolean isPalindrome(ListNode head) { //使用快慢指針將鏈表分紅兩個兩個指針 //注意中間節點的邊界條件 //偶數和奇數狀況須要區分對待 ListNode fast=head,slow=head; while(fast!=null&&fast.next!=null){ slow=slow.next; fast=fast.next.next; } //奇數狀況,slow 指針停在正好中間位置,須要再向後移動一個位置,使前半部分比後半部分長 1 //偶數時,slow 指針正好停在等分位置的下一個指針位置處,不須要進行額外操做,直接傳入反轉函數將後半部分進行反轉處理 //鏈表的長度爲奇數 if(fast!=null){ slow=slow.next; } //反轉後半部分鏈表 //重複利用 slow 指針,下一次遍歷 slow=reverse(slow); //重複利用 fast 指針,進行下一次的步進爲 1 的遍歷前半部分的鏈表 fast=head; //後半部分先遍歷結束 //和歸併排序的區別是不必徹底分開生成兩個鏈表,只須要判斷就行 while(slow!=null){ if(fast.val!=slow.val){ return false; } fast=fast.next; slow=slow.next; } return true; } //反轉鏈表的輔助方法 private ListNode reverse(ListNode head){ //一個指向尾節點的指針 ListNode tail=null; //由於返回的是尾節點,不必在設置一個 curr 指針了,直接用 head 進行移動就行了 while(head!=null){ ListNode nextTemp=head.next; head.next=tail; tail=head; head=nextTemp; } return tail; } }
237
//刪除指定值相應的節點,沒有給定輸入的頭結點,直接輸入的是要刪除節點的值 /** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public void deleteNode(ListNode node) { //爲何這裏須要將下一個結點的值賦值給要刪除的節點? //由於沒法得到上一個節點的信息,不能用上一個節點指向當前節點的下一個結點這種方法 //經過將當前節點的值變成下一個結點的值,再跳過下一個結點這種方法來實現 node.val=node.next.val; node.next=node.next.next; } }
094
//中序遍歷:給定一個二叉樹,返回其中序遍歷 //分別使用遞歸和迭代的方法 /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { public List<Integer> inorderTraversal(TreeNode root) { //使用集合數據結構存放結果 List<Integer> res=new ArrayList<>(); helper(root,res); return res; } //定義一個輔助方法用於實現遞歸 //爲了每次不從新創建新的集合存放結果 private void helper(TreeNode root,List<Integer> res){ //當根節點爲空時,不進行任何操做 if(root!=null){ if(root.left!=null){ helper(root.left,res); } //關鍵是在這一步將根節點的值加入到集合中 res.add(root.val); if(root.right!=null){ helper(root.right,res); } } } } //迭代方法 class Solution { public List<Integer> inorderTraversal(TreeNode root) { //使用到棧的迭代方法,保存的是值 List<Integer> res=new ArrayList<>(); //使用棧來存放向下遍歷過程當中通過的節點 Stack<TreeNode> stack=new Stack<>(); //用來臨時存放根節點 TreeNode curr=root; //遍歷完全部節點的循環終止條件爲何是 curr==null 和 stack 爲空呢? while(curr!=null||!stack.isEmpty()){ //一直找到最左子樹,在這個過程當中將遍歷過的樹節點加入到棧中 while(curr!=null){ stack.push(curr); //深刻到下一層左子樹 curr=curr.left; } //此時出棧的節點就是最左節點了 //pop 的做用是從棧中刪除並取得這一節點 curr=stack.pop(); res.add(curr.val); //深刻到下一層右子樹 curr=curr.right; } return res; } } class Solution { public List<Integer> inorderTraversal(TreeNode root) { //先將根節點入棧, // 一直往其左孩子走下去,將左孩子入棧,直到該結點沒有左孩子,則訪問這個結點,若是這個結點有右孩子,則將其右孩子入棧, // 重複找左孩子的動做,這裏有個要判斷結點是否是已經被訪問的問題。 非遞歸中序遍歷(效率有點低), // 使用map(用set貌似更合理)來判斷結點是否已經被訪問 List<Integer> res=new ArrayList<>(); //保存已經訪問過的節點 Map<TreeNode,Integer> map=new HashMap<>(); //將要訪問的節點 Stack<TreeNode> stack=new Stack<>(); if(root==null){ return res; } stack.push(root); while(!stack.isEmpty()){ //注意這裏是peek而不是pop,由於這一步是要一直遞歸到最左端而不是進行訪問 TreeNode tempNode=stack.peek(); //一直往左子樹深刻 while(tempNode.left!=null){ //若是沒有被訪問過加入到棧中而且繼續深刻,訪問過就退出 if(map.get(tempNode.left)!=null){ break; } stack.push(tempNode.left); tempNode=tempNode.left; } //訪問該節點,訪問事後就從棧中刪除並在map中添加,因此用的是pop方法 tempNode=stack.pop(); res.add(tempNode.val); map.put(tempNode,1); //右節點入棧 if(tempNode.right!=null){ stack.push(tempNode.right); } } return res; } } //前序遍歷的非遞歸方法 class Solution { public List<Integer> inorderTraversal(TreeNode root) { List<Integer> res=new ArrayList<>(); if(root==null){ return res; } Stack<TreeNode> stack=new Stack<>(); stack.push(root); while(!stack.isEmpty()){ //將棧頂元素彈出並保存 TreeNode tempNode=stack.pop(); if(tempNode!=null){ res.add(root.val); //注意是先添加右子樹節點再添加左子樹節點 stack.push(root.right); stack.push(root.left); } } return res; } } //後序遍歷的費遞歸方法 class Solution { public List<Integer> postorderTraversal(TreeNode root) { //先進行前序遍歷,再將結果鏈表倒置至關於後序遍歷返回結果 List<Integer> res=new ArrayList<>(); if(root==null){ return res; } Stack<TreeNode> stack=new Stack<>(); stack.push(root); while(!stack.isEmpty()){ //將棧頂元素彈出並保存 TreeNode tempNode=stack.pop(); if(tempNode!=null){ res.add(root.val); //注意是先添加右子樹節點再添加左子樹節點 stack.push(root.right); stack.push(root.left); } } Collections.reverse(res); return res; } } class Solution { public List<Integer> postorderTraversal(TreeNode root) { List<Integer> res=new ArrayList<>(); if(root==null){ return res; } Map<TreeNode,Integer> map=new HashMap<>(); Stack<TreeNode> stack=new Stack<>(); stack.push(root); while(!stack.isEmpty()){ TreeNode tempNode=stack.peek(); //若是當前節點是葉子結點或者左右節點均被訪問過 if(tempNode.left==null&&tempNode.right==null||(!((tempNode.left!=null&&map.get(tempNode.left)==null)||tempNode.right!=null&&map.get(tempNode.right)==null))){ res.add(tempNode.val); map.put(tempNode,1); stack.pop(); continue; } //當左節點不是空時 if(tempNode.left!=null){ //左節點沒有被訪問的狀況下,一直找到最左邊沒有被訪問的左節點 while(tempNode.left!=null&&map.get(tempNode.left)==null){ stack.push(tempNode.left); tempNode=tempNode.left; } } //當右節點不爲空時 if(tempNode.right!=null){ //每次只訪問一個右節點就好 if(map.get(tempNode.right)==null){ stack.push(tempNode.right); } } } return res; } } class Solution { public List<Integer> postorderTraversal(TreeNode root) { //使用兩個棧的方法 List<Integer> res=new ArrayList<>(); if(root==null){ return res; } Stack<TreeNode> stack=new Stack<>(); Stack<TreeNode> stack1=new Stack<>(); stack.push(root); while(!stack.isEmpty()){ //將棧頂元素彈出並保存 TreeNode tempNode=stack.pop(); stack1.push(tempNode); //先將左節點入棧1,再將右節點入棧1 //由於通過了兩個棧,後進先出的順序又反過來了,因此對比只有一個棧的前序遍歷 if(tempNode.left!=null){ stack.push(tempNode.left); } if(tempNode.right!=null){ stack.push(tempNode.right); } } while(!stack1.isEmpty()){ res.add(stack1.pop().val); } return res; } } //層序遍歷的非遞歸實現 class Solution { public List<List<Integer>> levelOrder(TreeNode root) { List<List<Integer>> res=new LinkedList<>(); //記錄層數 int height=0; //使用隊列存放節點 Queue<TreeNode> queue=new LinkedList<>(); queue.add(root); while(!queue.isEmpty()){ height=queue.size(); List<Integer> tempList=new ArrayList<>(); while(height>0){ TreeNode tempNode=queue.poll(); if(tempNode!=null){ tempList.add(tempNode.val); queue.add(tempNode.left); queue.add(tempNode.right); } height--; } if(tempList.size()>0){ res.add(tempList); } } return res; } }
095
//獨特的二叉搜索樹2:給定一個整數,生成用來存儲從 1 到這個整數的全部可能的二叉搜索樹的數據結構 //思路:將n個數字一直分紅兩部分,分開的中間節點一直增長,把這兩部分分別遞歸的添加到左右子樹中 //方法,遞歸或者動態規劃 //樹就是多了一個指向的鏈表 /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { public List<TreeNode> generateTrees(int n) { if(n==0){ return new ArrayList<>(); } //從開始參數到結束參數構造二叉樹 return generateHelper(1,n); } private List<TreeNode> generateHelper(int start,int end){ //爲何結果集合是在輔助方法中建立呢 List<TreeNode> list=new ArrayList<>(); //添加葉子結點也就是空節點的狀況 if(start>end){ list.add(null); return list; } //當左右子樹中只有一個節點時添加到集合中去 //將樹添加到集合中的過程 if(start==end){ //注意添加的是節點不是整數 list.add(new TreeNode(start)); return list; } //左右子樹分別保存在兩個由節點組成的節點集合中 List<TreeNode> left,right; //從開始到結束節點開始遍歷添加到整個二叉搜索樹之中 //在這裏就選定了每次遞歸的根節點 //創造樹的過程 for(int i=start;i<=end;i++){ //每次循環選定 i 做爲根節點,依次輸入左右子樹的開始和結束節點對應的值 //經過遞歸獲得相應的左右子樹節點的集合 left=generateHelper(start,i-1); right=generateHelper(i+1,end); //遍歷左右子樹組成不一樣形式的二叉樹 for(TreeNode lnode:left){ for(TreeNode rnode:right){ //構建臨時的根節點 //構建由三個節點組成的最基本的二叉樹,注意要知足二叉搜索樹的定義 TreeNode root=new TreeNode(i); root.left=lnode; root.right=rnode; list.add(root); } } } return list; } } //簡化版 public List<TreeNode> generateTrees(int n) { return generateSubtrees(1, n); } private List<TreeNode> generateSubtrees(int s, int e) { List<TreeNode> res = new LinkedList<TreeNode>(); if (s > e) { res.add(null); // empty tree return res; } for (int i = s; i <= e; ++i) { List<TreeNode> leftSubtrees = generateSubtrees(s, i - 1); List<TreeNode> rightSubtrees = generateSubtrees(i + 1, e); for (TreeNode left : leftSubtrees) { for (TreeNode right : rightSubtrees) { TreeNode root = new TreeNode(i); root.left = left; root.right = right; res.add(root); } } } return res; }
096
//獨特的二叉搜索樹1:給定 n 統計出結構上惟一的二叉樹的個數 //枚舉序列中的數字分別做爲根,遞歸的從子序列構造樹,經過這種方法能夠保證構造的樹是獨特的,由於具備獨特的根 //G(n) 使咱們要求的函數,F(i,n) 是當 i 做爲根時構成的惟一二叉搜索樹的數目,其中n表明總數,i表明當前的數字做爲根節點的狀況 //1\ G(n) = F(1, n) + F(2, n) + ... + F(n, n) 而且 G(0)=1, G(1)=1 //2\ F(i, n) = G(i-1) * G(n-i) 1 <= i <= n //3\ G(n) = G(0) * G(n-1) + G(1) * G(n-2) + … + G(n-1) * G(0) //以上三步也是嵌套循環與數學求和聯繫的過程 //指定根節點 F(i) 的可組成二叉樹的數目是其左右子樹的笛卡爾積 //動態規劃 class Solution { public int numTrees(int n) { //在基礎狀況,只有一種可能來描述只有艮或者空樹的狀況 int[] G=new int[n+1]; //基準狀況 G[0]=1; G[1]=1; //分別以 i 爲根節點的狀況 //其中用 G 來表示 F 來經過循環解決 for(int i=2;i<=n;i++){ for(int j=1;j<=i;j++){ //求笛卡爾積的過程 //假設當前根節點爲 3 總結點數爲 7,那麼右子樹的節點數爲 7-4+1=4 = 7-3 G[i]+=G[j-1]*G[i-j]; } } return G[n]; } }
098
//驗證二叉搜索樹:二叉搜索樹的定義,給定一個二叉樹,判斷其是否爲二叉搜索樹 //左子樹節點的鍵均小於根節點的鍵,右子樹節點的鍵均大於根節點的鍵,左右字數都是二叉搜索樹 //要保證左子樹的全部節點均小於根節點,就要設置左右的極限值。以便下一輪遍歷時做比較 /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { public boolean isValidBST(TreeNode root) { //空樹也是一個二叉搜索樹 if(root==null){ return true; } return isBSTHelper(root,null,null); } //遞歸的輔助方法 //參數 lower 和 upper 表明當前循環比較的一個極限值,樹的搜索每深刻一個層級,極限值也就跟着改變 private boolean isBSTHelper(TreeNode node,Integer lower,Integer upper){ //遞歸方法的基準條件通常都寫在這個遞歸遍歷語句的前半部分 if(lower!=null&&node.val<=lower){ return false; } if(upper!=null&&node.val>=upper){ return false; } //若是左子樹節點爲空,則返回 true ,不爲空就繼續往下一層深刻 //left 和 right 至關因而左右子樹分別的判斷是不是二叉搜索樹的標識符 boolean left=(node.left!=null)?isBSTHelper(node.left,lower,node.val):true; //若是對左子樹的遍歷返回爲真,則繼續遍歷右子樹,不然直接跳到 else 做用域返回 false if(left){ //根據具體狀況,右子樹的判斷值可能爲 true 也可能爲 false boolean right=(node.right!=null)?isBSTHelper(node.right,node.val,upper):true; return right; }else{ return false; } //等價於 boolean left=(root.left!=null)?helper(root.left,lower,root.val):true; boolean right=(root.right!=null)?helper(root.right,root.val,upper):true; return left&&right; } }
099***
//修復錯誤:二叉搜索樹中的兩個節點被錯誤的交換,在不改變樹結構的狀況下修復這一錯誤 //找出不對順序的第一第二個節點元素 /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { // //中序遍歷的最簡單形式 // private void traverse(TreeNode root){ // if(root==null){ // return; // } // traverse(root.left); // //作一些你須要的操做 // traverse(root.right); // } //全局變量 TreeNode firstElement=null; TreeNode secondElement=null; //將前項指針用最小的整數進行初始化 TreeNode prevElement=new TreeNode(Integer.MIN_VALUE); public void recoverTree(TreeNode root) { //中序遍歷整棵樹,在這個過程當中改變上面三個節點變量的值 traverse(root); //交換找出的 first 和 second 出錯變量的值 int temp=firstElement.val; firstElement.val=secondElement.val; secondElement.val=temp; } //目的就是找出順序錯誤的節點並存放到 first 和 second 全局變量中去 //使用的是中序遍歷 private void traverse(TreeNode root){ if(root==null){ return; } traverse(root.left); if(firstElement==null&&prevElement.val>=root.val){ firstElement=prevElement; } if(firstElement!=null&&prevElement.val>=root.val){ secondElement=root; } prevElement=root; traverse(root.right); } }
100
//相同的樹:檢查兩個二叉樹是否徹底相同,徹底相同的定義是結構上相同而且對應節點具備相等的值 //遞歸方法,比較簡單,迭代方法很難 /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { public boolean isSameTree(TreeNode p, TreeNode q) { //主要分紅三種狀況,兩棵樹全爲空,有一顆爲空,全不爲空 //這三個 if 語句共同構成這個遞歸的基準條件 if(p==null&&q==null){ return true; } if(p==null||q==null){ return false; } //不是判斷相等,是找出全部不等時的返回條件 if(p.val!=q.val){ return false; } //根節點相同的狀況下才繼續比較左右節點 return isSameTree(p.left,q.left)&&isSameTree(p.right,q.right); } }
101
//鏡像二叉樹:判斷一棵二叉樹是不是關於自身鏡像的,該樹的給出是經過前序遍歷的數組 //思路;判斷鏡像須要比較兩個節點的狀況,所以最好有一個輔助方法能夠傳入兩個參數,在傳入參數的時候作文章 //一樣是經過遞歸和迭代兩種方法解決,目前只考慮遞歸的解決方法 /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { public boolean isSymmetric(TreeNode root) { return isMirror(root,root); } //由於比較的時候輸入時兩個節點比較直接,經過輔助的遞歸方法實現 private boolean isMirror(TreeNode t1,TreeNode t2){ //由於葉子結點默認是空節點 if(t1==null&&t2==null){ return true; } if(t1==null||t2==null){ return false; } //這一步沒想起來 //注意是右子樹的左節點和左子樹的右節點進行比較 return (t1.val==t2.val)&&isMirror(t1.left,t2.right)&&isMirror(t1.right,t2.left); //和判斷兩個樹是否全等本質上時相同的 } }
102
//二叉樹的層序遍歷:返回二叉樹的層序遍歷,結果存放在集合中,每一層存放成一個子集合 //使用廣度優先搜索的方法,也能夠利用隊列 /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { public List<List<Integer>> levelOrder(TreeNode root) { //存放結果的集合 List<List<Integer>> res=new ArrayList<>(); helper(res,root,0); return res; } private void helper(List<List<Integer>> res,TreeNode root,int height){ //判斷是否到了葉子結點 //遞歸的基準條件 if(root==null){ return; } //當樹的遍歷進行到下一層時,須要向集合中添加新的鏈表 if(height>=res.size()){ res.add(new LinkedList<>()); } res.get(height).add(root.val); //由於無論是左子樹仍是右子樹的遞歸,只要在同一層遞歸中,所處的層次就是相同的,每次遞歸須要取出當前的層次 helper(res,root.left,height+1); helper(res,root.right,height+1); } }
103
//z字型的層序遍歷:層序遍歷的擴展題,按 z 字型返回二叉樹的每層節點值子集合組成的集合 //思路:經過一個標識符表示當前是奇數層仍是偶數層,一層一變號;不使用遞歸就得藉助額外的數據結構實現遞歸過程 //關於樹的解決方案中不少也用到了 bfs 和 dfs /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { public List<List<Integer>> zigzagLevelOrder(TreeNode root) { //標準的廣度優先實現方法 List<List<Integer>> res=new ArrayList<>(); if(root==null){ return res; } //queue 數據結構的內部就是徹底有 LinkedList 實現的 Queue<TreeNode> queue=new LinkedList<>(); //和添加鏈表中的頭結點相似,添加了根節點至關於把整棵樹都添加到了隊列之中,由於能夠經過根節點遍歷獲得整棵樹 queue.offer(root); //當層級數是奇數時,改變 reverse 標識符,這一層的樹節點將從後往前進行遍歷,也就實現了偶數行用 false 表示 boolean reverse=false; while(!queue.isEmpty()){ //遍歷節點的時候用到 int size=queue.size(); //使用臨時鏈表,將添加節點的時間複雜度降到 O1 //不能使用接口,否則解析不到 addFirst 這個方法 LinkedList<Integer> tempList=new LinkedList<>(); for(int i=0;i<size;i++){ TreeNode currNode=queue.poll(); //reverse 爲真,表示當前行是奇數行 if(reverse){ //接口沒有這個方法 //從頭部開始添加 //最新的在最前面,和棧的性質有點像,也就實現了奇數行從後向前遍歷 tempList.addFirst(currNode.val); }else{ //添加到尾部,最新添加的元素在最後面 tempList.add(currNode.val); } //依次向下遍歷左右子樹 //隊列數據結構可以始終保證下一層元素在當前層元素後面入隊 if(currNode.left!=null){ queue.offer(currNode.left); } if(currNode.right!=null){ queue.offer(currNode.right); } } res.add(tempList); //沒深刻一個層次,標識符變換一次符號 reverse=!reverse; } return res; } }
104
//求一個二叉樹的最大深度,也就是從根節點到葉子結點的最長路徑所包含的節點數 //分別使用,遞歸,迭代的方法又分別可使用深度優先所搜和廣度優先搜索的方法, /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { public int maxDepth(TreeNode root) { if(root==null){ return 0; } //1 表明的是當前節點,最長路徑必然包含當前節點 return 1+Math.max(maxDepth(root.left),maxDepth(root.right)); } }
107
//從底向上按照層級存放樹的結構到集合中並返回 //分別使用深度優先和廣度優先的方法 //深度優先算法爲使用遞歸,廣度優先算法使用了遞歸 //也是至關於層序遍歷 /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { public List<List<Integer>> levelOrderBottom(TreeNode root) { //何時須要使用隊列進行存放呢 Queue<TreeNode> queue=new LinkedList<>(); //須要返回的結果集合 List<List<Integer>> res=new LinkedList<>(); if(root==null){ return res; } //至關於將整棵樹存入到 queue 中 queue.offer(root); //與以前 z 字型層序遍歷的解法相似 while(!queue.isEmpty()){ int size=queue.size(); //臨時存放一層節點的鏈表 //在 for 循環外邊定義 //將元素添加到鏈表中 List<Integer> tempList=new LinkedList<>(); for(int i=0;i<size;i++){ //由於臨時保存了當前節點的信息,因此先添加仍是先判斷左右子樹均可以執行 TreeNode currNode=queue.poll(); if(currNode.left!=null){ queue.offer(currNode.left); } if(currNode.right!=null){ queue.offer(currNode.right); } //具體存放節點的值的位置 tempList.add(currNode.val); //與上面代碼是等價的 // if(queue.peek().left != null) queue.offer(queue.peek().left); // if(queue.peek().right != null) queue.offer(queue.peek().right); // subList.add(queue.poll().val); } //插入到第一個位置,至關於棧 //將鏈表添加到結果集合的過程 res.add(0,tempList); //將 res 由 List 變成 LinkedList 能夠直接使用 addFirst 方法,效果是同樣的 } return res; } } //bfs class Solution { public List<List<Integer>> levelOrderBottom(TreeNode root) { List<List<Integer>> res=new LinkedList<>(); helper(res,root,0); return res; } //爲了使程序更符合人的邏輯 private void helper(List<List<Integer>> res,TreeNode root,int height){ if(root==null){ return; } if(height>=res.size()){ //在結果集合中新創建的一條鏈表 res.add(0,new LinkedList<>()); } //bfs 是從最底層開始向上填充數據的 helper(res,root.left,height+1); helper(res,root.right,height+1); //由於前面插入新鏈表的時候是按照棧的方式來的, res.get(res.size()-height-1).add(root.val); } }
108
//給定一個升序排列的數組,將其轉變成平衡的二叉搜索樹 //至關於給定的是二叉樹的中序遍歷,經過中序遍歷肯定根節點的位置 //有序數組至關於中序遍歷的輸出結果 /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { public TreeNode sortedArrayToBST(int[] nums) { if(nums.length==0){ return null; } TreeNode root=helper(nums,0,nums.length-1); return root; } //後面的兩個參數至關於兩個指針,用來肯定組成子樹的節點的左右端點 //這是從底向上構造的 private TreeNode helper(int[] nums,int lo,int hi){ //遞歸的結束條件,直到葉子節點終止 //返回 null 做爲葉子結點 if(lo>hi){ return null; } //找到組成子樹的這段數組中根節點,也就是數組的中點 int mid=lo+(hi-lo)/2; //構造出根節點 TreeNode root=new TreeNode(nums[mid]); root.left=helper(nums,lo,mid-1); root.right=helper(nums,mid+1,hi); return root; } }
110
//判斷一個二叉樹是不是平衡二叉樹 //兩種解決方法,大體是從上向下和從下向上的解決 /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ //分析:須要遍歷每個節點,時間複雜度是 O(N^2) class Solution { public boolean isBalanced(TreeNode root) { if(root==null){ return true; } //左子樹的高度和右子樹的高度 int left=helper(root.left); int right=helper(root.right); //遞歸判斷當前節點是不是平衡的而且它的左右節點是否也是平衡的 return (Math.abs(left-right)<=1)&&isBalanced(root.left)&&isBalanced(root.right); } //輔助方法,返回當前根節點的高度 private int helper(TreeNode root){ if(root==null){ return 0; } return 1+Math.max(helper(root.left),helper(root.right)); } } //分析:樹中的每一個節點只須要訪問一次。所以,時間複雜度爲O(N),優於第一解決方案。 class Solution { public boolean isBalanced(TreeNode root) { //基於DFS。咱們不是爲每一個子節點顯式調用depth(),而是在DFS遞歸中返回當前節點的高度 return helper(root)!=-1; } //輔助方法顯示的判斷是不是平衡的,平衡的返回當前節點的高度,不平衡的返回 -1 private int helper(TreeNode root){ if(root==null){ return 0; } //左子樹節點的高度 int left=helper(root.left); //判斷左子樹是不是平衡的二叉樹 if(left==-1){ return -1; } //右子樹節點的高度 int right=helper(root.right); if(right==-1){ return -1; } //判斷當前節點構成的樹是不是平衡的二叉樹 if(Math.abs(left-right)>1){ return -1; } //程序執行到這一步說明是平衡的節點構成的樹,所以返回該節點的高度 //因此計算節點高度和判斷當前節點是不是平衡的樹在一段遞歸程序中執行了,只遍歷了一次全部節點,下降了時間複雜度 return 1+Math.max(left,right); } }
111
//計算一個給定二叉樹的最小深度,也就是根節點到達葉子結點的最短路徑所包含的節點數目 /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ //這是一種由底向上的方法 class Solution { public int minDepth(TreeNode root) { //和以前判斷是不是平衡的二叉樹 bfs 解決方案有類似的地方 if(root==null){ return 0; } //左子樹的最小深度 int left=minDepth(root.left); //右子樹的最小深度 int right=minDepth(root.right); //分爲三種狀況:右子樹和右子樹全爲空返回的就都是 0 ,返回只是表明當前節點的層數 1;其中有一邊的子樹爲 0 ,返回的就是另外一邊的最小深度+1;兩邊全不爲 0 ,返回的則是較小的一邊的深度+1 return (left==0||right==0)?(1+right+left):(1+Math.min(left,right)); } }
112
//給定一個二叉樹和一個目標值,判斷是否含有一條從根節點到葉子結點的路徑使得節點的值相加等於目標值 /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { public boolean hasPathSum(TreeNode root, int sum) { //何時表示沒有找到相應的路徑 //當 root 節點變成空時,也就是到了葉子結點,sum 仍然沒有減小到 0,說明不存在這樣的路徑 if(root==null){ return false; } //何時表示找到相應的路徑 //必須當前節點就是葉子結點,意味着當前節點的左右子樹全是空,而且 sum 值正好減小到 0,則找到相應的路徑 //注意是減去當前節點的值以後等於 0 if(root.left==null&&root.right==null&&sum==root.val){ return true; } //像這種判斷是否含有相加和的問題,在遞歸中使用的是減法 //只要存在一條這樣的路徑就行,因此用或操做符 return hasPathSum(root.left,sum-root.val)||hasPathSum(root.right,sum-root.val); } }
113
//給定一個二叉樹和一個目標值,判斷是否含有一條從根節點到葉子結點的路徑使得節點的值相加等於目標值,返回由這些路徑組成的集合 //和以前作過的找出數組中全部可能的子數組很是類似 /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { public List<List<Integer>> pathSum(TreeNode root, int sum) { List<List<Integer>> res=new LinkedList<>(); pathSum(res,new LinkedList<>(),root,sum); return res; } private void pathSum(List<List<Integer>> res,List<Integer> tempList,TreeNode root,int sum){ //表示不含有這樣路徑的狀況 if(root==null){ return; } tempList.add(root.val); //找到一條可行的路徑的時候 if(root.left==null&&root.right==null&&sum==root.val){ res.add(new LinkedList<>(tempList)); //這裏用到了回溯 tempList.remove(tempList.size()-1); //找到了就返回 //因此是兩種狀況,一種是找到相應的路徑,一種是沒有找到相應的路徑,都得返回 return; }else{ pathSum(res,tempList,root.left,sum-root.val); pathSum(res,tempList,root.right,sum-root.val); } tempList.remove(tempList.size()-1); } }
114
//將一顆二叉樹轉換成鏈表 //遞歸方法從下往上拼接 /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { public void flatten(TreeNode root) { if(root==null){ return; } //暫時保存當前樹節點的左右子樹,等同於鏈表中暫時保存下一節點 TreeNode leftTree=root.left; TreeNode rightTree=root.right; //遞歸的進行左右子樹,直到葉子結點 flatten(leftTree); flatten(rightTree); //將左子樹置爲空 root.left=null; //將以前保存的左子樹部分接到右子樹 root.right=leftTree; //保存當前的根節點,防止之後用到 //直接用 root 指針進行移動也是能夠的 //必定要從 root 開始移動若是從 leftNode 開始,則會出現空指針異常 //爲何必定要從 root 開始呢??? TreeNode curr=root; //將指針移動到右子樹的最後一個節點 while(curr.right!=null){ curr=curr.right; } //將以前保存的右子樹拼接到最後 curr.right=rightTree; } }
116
//一個滿二叉樹而且葉子結點都在同一層級上,每一個父節點均有兩個子節點,而且具備兄弟節點,next 指針指向當前節點的右邊的節點 //只能使用固定的額外空間,遞歸方法的隱式堆棧不算做額外空間 //該題目的目的是爲節點增長向右的指針 /* // Definition for a Node. class Node { public int val; public Node left; public Node right; public Node next; public Node() {} public Node(int _val,Node _left,Node _right,Node _next) { val = _val; left = _left; right = _right; next = _next; } }; */ class Solution { public Node connect(Node root) { //非空判斷不能少,由於第一層循環的空值 left 會出空指針異常 if(root==null){ return root; } //一層一層向下遍歷,先找到每一層的最左端節點 //須要臨時存儲每層最左端的節點做爲頭,由於鏈表沒有向前的指針 //一個保存當前層次的頭結點,一個向後移動 Node prev=root; Node curr=null; while(prev.left!=null){ //此時是上一層的最左端節點 curr=prev; //第一次進入循環 //在一層上向右移動直到空節點 while(curr!=null){ //上一層的右子樹爲這一層的左子樹的下一個結點 curr.left.next=curr.right; //用到了三個節點進行拼接 if(curr.next!=null){ curr.right.next=curr.next.left; } //當前層的指針向後移動一位 curr=curr.next; } //左子樹的指針向左下移動一個位置 prev=prev.left; } return root; } }
117
//給定一個不是完美的二叉樹,也就是不必定每個父節點均有兩個子節點,?但每一層的均有右子樹? /* // Definition for a Node. class Node { public int val; public Node left; public Node right; public Node next; public Node() {} public Node(int _val,Node _left,Node _right,Node _next) { val = _val; left = _left; right = _right; next = _next; } }; */ class Solution { //根節點的 next 怎麼添加 null 節點的 public Node connect(Node root) { //使用層序遍歷的方法 //爲了能直接返回根節點,使用虛擬根節點,和以前鏈表的題目殊途同歸 Node dummy=new Node(0); Node prev=root,curr=dummy; //判斷是否到了葉子結點的後面節點 while(prev!=null){ //判斷是否到達了這一層級的最後一個節點 while(prev!=null){ if(prev.left!=null){ curr.next=prev.left; curr=curr.next; } if(prev.right!=null){ curr.next=prev.right; curr=curr.next; } //這一層的左右節點都添加到了新的層級 prev=prev.next; } //由於 curr 指針已經將結點添加到 dummy 後面 //至關於將上一層的左子樹賦值給 prev 指針 prev=dummy.next; //從新初始化虛擬根節點和 curr 指針,用於下一層級的遍歷 dummy.next=null; curr=dummy; } return root; } }
124
//給定一個非空的二叉樹,尋找最大路徑的總和,一個路徑定義爲一個從書中任意節點到另外一個節點的序列,不必定包含根節點,至少含有一個節點 /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { //設置一個類成員變量的最大值,只要輔助方法中對其進行了改變,主方法中的 max 也會賦予新的值 //通常這種求最大值的問題,初始化爲整數中的最小值 //也就不須要在輔助函數中返回 max 了,輔助函數調用的是遞歸,更加方便的編寫程序 int max=Integer.MIN_VALUE; public int maxPathSum(TreeNode root) { helper(root); return max; } //幫助返回最大的分支,添加當前節點的值到路徑上 private int helper(TreeNode root){ if(root==null){ return 0; } //依次向下找出左子樹和右子樹的最大路徑 int left=Math.max(helper(root.left),0); int right=Math.max(helper(root.right),0); //判斷通過當前節點的值是不是最大的值 max=Math.max(max,root.val+left+right); //返回的是當前節點的值加上左子樹或者右子樹中較大的路徑的值 return root.val+Math.max(left,right); } }
129
//設置一個類成員變量存放結果值,初始化能夠在這裏,也能夠在函數中進行 //由於返回的 res 只有一個,而且每一條路徑組成的數字的值都須要傳入到下一層循環中才行,所以須要一個輔助方法將攜帶每條路徑數字信息的值傳入 //輔助函數沒有返回值,只是對 res 的值進行每次的添加改變 /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { //設置一個類成員變量存放結果值,初始化能夠在這裏,也能夠在函數中進行 int res; public int sumNumbers(TreeNode root) { res=0; //存放一條路徑組成的數字的變量 int sum=0; helper(root,sum); return res; } //一層一層遞進的過程當中,上一層的值經過 sum 變量傳入,不須要返回值 private void helper(TreeNode root,int sum){ if(root==null){ return; } //利用參數 sum 攜帶的上一層的節點值信息,計算當前路徑組成的數字大小 //越是上層表明着越高的位次 sum=sum*10+root.val; //達到葉子結點,能夠進行返回了,以後遞歸計算左右子樹的狀況 if(root.left==null&&root.right==null){ //將一條路徑組成的數字加到總和上面 res+=sum; return; } //分別對左右子樹進行遞歸操做 //趕上一條語句誰在前執行都沒有關係 helper(root.left,sum); helper(root.right,sum); } }
144
//給定一個二叉樹,返回它的節點值的前序遍歷 //分別使用遞歸和迭代的方法完成 //返回值是一個集合,同時由於有 addAll 方法,因此不用輔助方法經過原方法的遞歸就能實現 /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { public List<Integer> preorderTraversal(TreeNode root) { //遞歸的方式,中左右的順序添加節點就行了 //注意每次均會建立一個新的結合來存儲節點元素 List<Integer> res=new ArrayList<>(); //在當前節點不是空的時候才執行,若當前節點爲空,則不執行任何代碼直接返回 if(root!=null){ res.add(root.val); res.addAll(preorderTraversal(root.left)); res.addAll(preorderTraversal(root.right)); } //返回的是集合,因此添加的時候經過 addAll 方法直接添加集合 return res; } } //給定一個二叉樹,返回它的節點值的前序遍歷 //分別使用遞歸和迭代的方法完成 /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { public List<Integer> preorderTraversal(TreeNode root) { //經過非遞歸的方法 //使用棧做爲存放中間節點的數據結構 List<Integer> res=new ArrayList<>(); if(root==null){ return res; } //使用隊列能夠嗎? Stack<TreeNode> stack=new Stack<>(); //將根節點壓入棧中 stack.push(root); while(!stack.isEmpty()){ TreeNode currNode=stack.pop(); res.add(currNode.val); //根據棧的先進後出原則,應該先將右子樹壓入棧中,才能在以後先出棧 //爲何試驗了一下,交換左右子樹的入棧順序後,結果沒有區別呢? if(currNode.right!=null){ stack.push(currNode.right); } if(currNode.left!=null){ stack.push(currNode.left); } } return res; } }
145
//和前序遍歷的代碼極其類似,只是將添加節點元素的值放到了遞歸左右子樹的後面而已 //返回一個給定二叉樹的後序遍歷 /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { public List<Integer> postorderTraversal(TreeNode root) { List<Integer> res=new ArrayList<>(); //加上這樣的非空判斷,能夠去掉後面的 if 條件判斷 //if(root==null){ // return res; //} if(root!=null){ res.addAll(postorderTraversal(root.left)); res.addAll(postorderTraversal(root.right)); res.add(root.val); } return res; } }
173
//在二叉搜索樹上面實現迭代器,經過根節點進行初始化 //注意題目中是二叉搜索樹,而且 next 方法返回的是下一個最小的元素,也就是按順序返回 //二叉搜索樹至關於一個有序數組的中序遍歷 //想一想用什麼數據結構來實現 /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class BSTIterator { //由於每一個方法中都用了了 stack 並且是對同一個 stack 進行操做,所以應該設置爲類方法變量 private Stack<TreeNode> stack=new Stack<>(); //將全部的左子樹加入到棧中的方法 private void pushAll(TreeNode root){ //注意何時用 if 判斷,何時用 while while(root!=null){ stack.push(root); root=root.left; } } //構造迭代器的過程就至關於將全部左子樹入棧的過程 public BSTIterator(TreeNode root) { pushAll(root); } /** @return the next smallest number */ //每次 next 方法用到的只是當前節點右子樹的左節點 public int next() { //從棧中彈出最靠左端的節點 TreeNode currNode=stack.pop(); //考慮只有三個節點的狀況下是怎麼樣進行的遍歷,在進行推理 pushAll(currNode.right); return currNode.val; } /** @return whether we have a next smallest number */ //棧爲空,說明已經把葉子節點進行了出棧操做 public boolean hasNext() { return !stack.isEmpty(); } } /** * Your BSTIterator object will be instantiated and called as such: * BSTIterator obj = new BSTIterator(root); * int param_1 = obj.next(); * boolean param_2 = obj.hasNext(); */
199
//給定一個二叉樹,想象一下本身站在這棵樹的最右邊,從上到下的返回你能看到的最右邊那一列的節點的值,也就是得到二叉樹的右側視圖 //分別使用深度優先和廣度優先搜索的方法實現 //時間複雜度和空間複雜度均是 On /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { public List<Integer> rightSideView(TreeNode root) { //深度優先搜索,咱們老是首先訪問最右側的節點,保存每一層的值,一旦得到了最終的層數,咱們就可以得到最終的結果數組 //遞歸經過存放節點的棧間接實現 //左右子樹都是一樣的這種操做 //使用 map 保存深度與其最右邊節點的映射關係 Map<Integer,Integer> rightmostValueAtDepth=new HashMap<>(); //兩個同步操做的棧分別保存深度和相應的節點 Stack<TreeNode> nodeStack=new Stack<>(); Stack<Integer> depthStack=new Stack<>(); nodeStack.push(root); depthStack.push(0); //最大的深度 int max_depth=Integer.MIN_VALUE; while(!nodeStack.isEmpty()){ TreeNode node=nodeStack.pop(); int depth=depthStack.pop(); //當前節點不爲空時,將判斷是否須要進行 map 更新,兩個棧的值必須更新 //注意判斷的是當前節點是否爲空 if(node!=null){ max_depth=Math.max(max_depth,depth); //map 中的映射是否須要更新須要判斷以前是否存入過這一深度的值 if(!rightmostValueAtDepth.containsKey(depth)){ rightmostValueAtDepth.put(depth,node.val); } //將左右子樹分別入棧,深度棧也進行加 1 操做 nodeStack.push(node.left); nodeStack.push(node.right); depthStack.push(depth+1); depthStack.push(depth+1); } } //通過前面的過程,最右邊的節點以及深度均存放在的 map 中 //map 中存放數據並不用考慮前後的順序 //構造輸出的結果 List<Integer> res=new ArrayList<>(); //最大深度就是在這裏發揮做用,由於僅僅經過 map 是沒法直接知道最大深度的 for(int depth=0;depth<=max_depth;depth++){ res.add(rightmostValueAtDepth.get(depth)); } return res; } } //廣度優先搜索的方法 class Solution { public List<Integer> rightSideView(TreeNode root) { //廣度優先算法和樹的層序遍歷很是類似 //和以前的深度優先算法結構上相似,只是將存放節點和深度的數據結構把棧換成隊列 //注意建立隊列時右邊的 new 的對象是什麼 Map<Integer,Integer> rightmostValueAtDepth=new HashMap<>(); Queue<TreeNode> nodeQueue=new LinkedList<>(); Queue<Integer> depthQueue=new LinkedList<>(); nodeQueue.add(root); depthQueue.add(0); int max_depth=Integer.MIN_VALUE; while(!nodeQueue.isEmpty()){ TreeNode node=nodeQueue.remove(); int depth=depthQueue.remove(); //對於 max 的賦值也要放在 if 做用域裏面,否則會多統計一個 null 節點 if(node!=null){ max_depth=Math.max(max_depth,depth); rightmostValueAtDepth.put(depth,node.val); nodeQueue.add(node.left); nodeQueue.add(node.right); //這裏的深度爲何要添加兩次呢?添加兩次並非在 depth 的基礎上加 2 而是一個節點的左右節點均在同一層級上,須要在 queue 中保存兩個一樣層級的信息 depthQueue.add(depth+1); depthQueue.add(depth+1); } } List<Integer> res=new ArrayList<>(); for(int depth=0;depth<=max_depth;depth++){ res.add(rightmostValueAtDepth.get(depth)); } return res; } }
222
//給定一個徹底二叉樹,統計其節點數目,節點的添加時從層級的左向右進行的,除了最後一層,其他均是滿二叉樹 //當前節點構成的樹的總結點數等於左右子樹的狀況加上當前節點 //注意用到了滿二叉樹的節點總數與樹高度的關係 /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { public int countNodes(TreeNode root) { //使用兩個輔助方法分別計算,左右子樹的深度,利用深度與節點個數之間的數學關係式計算出總的節點個數 //具體說輔助方法計算的不是節點深度,而是最左邊節點的深度,和最右邊節點的深度,判斷當前節點構成的樹是不是滿二叉樹 int leftmostDepth=leftDepth(root); int rightmostDepth=rightDepth(root); //判斷是不是滿二叉樹 if(leftmostDepth==rightmostDepth){ //return (1 << leftDepth) - 1; 使用位操做對代碼進行優化 //不能有 2*leftmostDepth-1 進行簡單的替換,徹底是兩回事 //等價的數學表達式爲 2的深度次方減 1 return (1 << leftmostDepth) - 1; }else{ return 1+countNodes(root.left)+countNodes(root.right); } } //最右子樹的深度 private int rightDepth(TreeNode root){ int depth=0; //葉子結點的高度爲 1 //使用 while 語句,不到空節點就一直往下進行遍歷 //只須要將指針移動到空指針就行,不用其餘操做 while(root!=null){ //錯誤是在這使用了遞歸,, root=root.right; depth++; } return depth; } private int leftDepth(TreeNode root){ int depth=0; while(root!=null){ root=root.left; depth++; } return depth; } }
226
//將二叉樹進行左右鏡像轉換,也就是反轉二叉樹 //分別有遞歸和迭代的方法,發現凡是將遞歸方法轉換成迭代方法的過程,均使用到了其餘數據結構代替遞歸的過程,多是棧也多是隊列,這要視狀況而定 /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { public TreeNode invertTree(TreeNode root) { // if(root!=null){ // TreeNode leftTemp=invertTree(root.left); // TreeNode rightTemp=invertTree(root.right); // root.left=rightTemp; // root.right=leftTemp; // } // return root; //上面是等價形式 if(root==null){ return null; } //利用兩個臨時變量保存轉換後的左右子樹的信息 //到達葉子結點的商議後至關於把一個有三節點構成的樹的左右節點交換的問題 TreeNode leftTemp=invertTree(root.left); TreeNode rightTemp=invertTree(root.right); root.left=rightTemp; root.right=leftTemp; //交換以後返回其父節點 return root; } }
230
//給定一個二叉搜索樹,返回其中第 k 小的節點元素,k 的範圍大於等於 1 小於等於樹的總結點數目 //使用中序遍歷的方法,分別又能夠遞歸和迭代實現, //中序遍歷的意思就是在遞歸遍歷左右子樹之間進行數據的操做 //提到二叉搜索樹的時候咱們就要考慮中序遍歷 /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { //使用類的全局變量,保存結果和計數變量 //一旦遇到讓求最小或者第幾小的狀況,均可以使用整數中的最小值對結果變量進行初始化 private int res=Integer.MIN_VALUE; private int count=0; public int kthSmallest(TreeNode root, int k) { inorderTraversal(root,k); //若是不使用類全局標量,在這一步將很難取得 res 的結果值 return res; } //中序遍歷的方法是沒有返回值的,而且遍歷以後將造成一個升序排列的數組 //只有在樹是二叉搜索樹的條件下這種狀況才成立 private void inorderTraversal(TreeNode root,int k){ if(root==null){ return; } inorderTraversal(root.left,k); //爲何在這個地方計數變量加 1? //只要在這一層的遞歸語句的做用域內,都表明當前遍歷的是一個節點,將計數變量的值加 1 就行了 count++; if(k==count){ res=root.val; } inorderTraversal(root.right,k); } } //迭代的解決方法,使用棧代替遞歸的過程 //未懂 public int kthSmallest(TreeNode root, int k) { Stack<TreeNode> stack = new Stack<TreeNode>(); TreeNode p = root; int count = 0; while(!stack.isEmpty() || p != null) { if(p != null) { stack.push(p); // Just like recursion p = p.left; } else { TreeNode node = stack.pop(); if(++count == k) return node.val; p = node.right; } } return Integer.MIN_VALUE; }
235
//給定一個二叉搜索樹,找到給定的倆個節點的值表明的節點的最小公共節點,節點的後代也包括它自身,二叉搜索樹中沒有相同的元素值 //分別有遞歸和迭代的方法 /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { //使用遞歸的方法,由於是二叉搜索樹,在判斷條件中應該會出現對於節點左右和當前節點值的比較 //分爲三種狀況,兩個輸入均在當前節點的左邊,都在當前節點的右邊 //第三種狀況是等於其中一個等於當前節點或者兩個輸入在當前節點的兩側,表明當前節點即爲要找的節點 //要找的節點再當前節點的右邊 if(p.val>root.val&&q.val>root.val){ //每一個做用域內老是要返回值的,以便構成遞歸 return lowestCommonAncestor(root.right,p,q); }else if(p.val<root.val&&q.val<root.val){ return lowestCommonAncestor(root.left,p,q); }else{ return root; } } } //迭代就是經過遞歸實現 class Solution { public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { while(root!=null){ if(p.val>root.val&&q.val>root.val){ root=root.right; }else if(p.val<root.val&&q.val<root.val){ root=root.left; }else{ return root; } } return null; } }
236
//此次給定的樹不是二叉搜索樹,而是一棵普通的二叉樹,找出給定的兩個節點的最小公共父節點 //分爲遞歸和迭代的方法,迭代又分爲設置父指針的迭代不不含有父指針的迭代 /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { //定義一個類全局變量使得兩個方法均能使用 private TreeNode res; public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { //首先基於深度優先的遍歷,經過標誌符號判斷子樹中是否含有給定的節點,判斷符號兩個都是 true 的時候,說明這個節點就是咱們想找的節點 traverseTree(root,p,q); return res; } //這個返回值是在遞歸中使用,而不是在主方法中使用,主方法中利用的是在給 res 賦值的時候 private boolean traverseTree(TreeNode root,TreeNode p,TreeNode q){ if(root==null){ return false; } //對整棵樹進行了遍歷,因此最後的 res 值就是咱們想要找的節點 //若是左遞歸中含有目標節點中的一個,返回 1,不然 0,右遞歸同理 int left=traverseTree(root.left,p,q)?1:0; int right=traverseTree(root.right,p,q)?1:0; //判斷是否給出的節點就是父節點自己 int mid=(root==p||root==q)?1:0; //關鍵是對 1 和 0 的標誌的利用,怎麼經過簡單的標誌位反映是否含有相應的節點的信息 if(mid+left+right==2){ res=root; } //若是是 0 說明沒有兩個爲 1 的標識符 //爲何相加結果爲 1 的狀況也知足呢? return (mid+left+right!=0); } }
257
//給定一個二叉樹,返回它的全部從跟到葉子結點的路徑 //使用遞歸的方法,考慮是否須要輔助函數進行遞歸,要是能在一個函數中進行遞歸那是最好不過的了 /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { public List<String> binaryTreePaths(TreeNode root) { //到達空節點時怎麼處理 List<String> paths=new LinkedList<>(); if(root==null){ return paths; } //到達葉子結點的時候要怎麼作 if(root.left==null&&root.right==null){ paths.add(root.val+""); return paths; } //最終到達的左子樹的葉子結點有多少個,就有多少個涉及左子樹的路徑 //遞歸在以集合的形式進行,和以前的 addAll 方法同樣的效果 for(String path:binaryTreePaths(root.left)){ paths.add(root.val+"->"+path); } //右子樹的狀況和左子樹同樣 for(String path:binaryTreePaths(root.right)){ paths.add(root.val+"->"+path); } return paths; } }
297
//序列化和反序列化二叉樹:設計一種算法能夠將二叉樹進行序列化和反序列化,使用什麼方法進行沒有特殊要求,只須要可以成功的進行轉換就行 //不能使用全局變量或者靜態變量來存放狀態,設計的序列化算法應該是無狀態的 //將二叉樹轉換成字符串 /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ public class Codec { //使用類變量存放分隔符 private static final String spliter=","; //使用類變量存放表明空值的X private static final String NN="X"; // Encodes a tree to a single string. public String serialize(TreeNode root) { StringBuilder sb=new StringBuilder(); buildString(root,sb); return sb.toString(); } //進行序列化時的輔助方法 private void buildString(TreeNode node,StringBuilder sb){ if(node==null){ sb.append(NN).append(spliter); }else{ sb.append(node.val).append(spliter); buildString(node.left,sb); buildString(node.right,sb); } } // Decodes your encoded data to tree. public TreeNode deserialize(String data) { //使用隊列來存放節點元素 Deque<String> nodes=new LinkedList<>(); nodes.addAll(Arrays.asList(data.split(spliter))); return buildTree(nodes); } //反序列化的輔助函數 //使用了回溯法 private TreeNode buildTree(Deque<String> nodes){ String val=nodes.remove(); if(val.equals(NN)){ return null; }else{ //該方法表示字符串指定的整數值,和parseInt(java.lang.String)方法返回一個整數值相似 TreeNode node=new TreeNode(Integer.valueOf(val)); node.left=buildTree(nodes); node.right=buildTree(nodes); return node; } } } // Your Codec object will be instantiated and called as such: // Codec codec = new Codec(); // codec.deserialize(codec.serialize(root));
005
//給定一個字符串,找出其中的最長迴文數 //使用將字符串倒置比較公共子串的方法是不正確的,由於當字符串變長時,不免出現巧合,使得公共子串不是迴文字符串 //使用動態規劃的方法 //必定要先弄清 dp[i][j] 表明的是從 i 到 j 位置處的子字符串是不是迴文字符串 class Solution { public String longestPalindrome(String s) { //防止輸入的是 "" if(s.length()==0){ return s; } //動態規劃的二維數組表明從 i 到 j 索引是不是一個迴文字符串, //注意區分數組,字符串,集合的求長度方法 int n=s.length(); boolean[][] dp=new boolean[n][n]; //返回的最長的迴文子串 String res=null; //爲何從最後一個字符開始遍歷 for(int i=n-1;i>=0;i--){ for(int j=i;j<n;j++){ //是一個迴文字符串的條件是先後向內移動一個字符的位置仍然是迴文,而且先後的當前字符相等 //當先後比較的字符位置相差0,1,2時,只要當前字符相等,那就是一個迴文字符串 //只要前面的條件知足了,只要位置相差在3之內就都是迴文字符串 dp[i][j]=(s.charAt(i)==s.charAt(j))&&(j-i<3||dp[i+1][j-1]); //若是找到一個迴文字符串,比較與存放在結果變量中的字符串長度,若是比其長,則替換 //若是隻是第一次查找,res 是 null 則直接替換 //在這一行報空指針異常是由於沒把res=null考慮進來 if(dp[i][j]&&(res==null||j-i+1>res.length())){ //取自字符串的時候,右邊索引不包含在內 res=s.substring(i,j+1); } } } return res; } }
010
//官方給的這種答案沒有看懂 //實現正則表達式中 . 和 * 的功能,功能分別是匹配單字符和0或者屢次的同一個字符匹配,匹配的是整個字符串而不是部分 //給定一個字符串和一個給定的模式,判斷兩者是否符合正則表達式 //方法分爲兩種,遞歸和動態規劃,動態規劃又分爲自頂向下和自底向上的實現 class Solution { //s 表明文本,p 表明模式 public boolean isMatch(String s, String p) { //使用動態規劃,自底向上的方法。由於這個問題有最佳子結構?因此緩存中間結果是很天然的,dp[i][j]表明文本 i 是否與模式 j 相匹配 //也就是從最後一個字符開始進行處理 int textLen=s.length(); int pattLen=p.length(); boolean[][] dp=new boolean[textLen+1][pattLen+1]; //存放最終結果的位置是 dp 的第一行第一列個元素 dp[textLen][pattLen]=true; for(int i=textLen;i>=0;i--){ for(int j=pattLen-1;j>=0;j--){ //對處理的第一個字符進行比較看是否匹配 //首先索引沒超過文本的長度,而文本與模式匹配的字符狀況有兩種,一種是相等,一種是模式的當前字符是 . boolean first_match=(i<textLen&&((p.charAt(j)==s.charAt(i))||p.charAt(j)=='.')); if(j+1<pattLen&&p.charAt(j+1)=='*'){ dp[i][j]=dp[i][j+2]||first_match&&dp[i+1][j]; }else{ dp[i][j]=first_match&&dp[i+1][j+1]; } } } return dp[0][0]; } } // 1, If p.charAt(j) == s.charAt(i) : dp[i][j] = dp[i-1][j-1]; // 2, If p.charAt(j) == '.' : dp[i][j] = dp[i-1][j-1]; // 3, If p.charAt(j) == '*': // here are two sub conditions: // 1 if p.charAt(j-1) != s.charAt(i) : dp[i][j] = dp[i][j-2] //in this case, a* only counts as empty // 2 if p.charAt(i-1) == s.charAt(i) or p.charAt(i-1) == '.': // dp[i][j] = dp[i-1][j] //in this case, a* counts as multiple a // or dp[i][j] = dp[i][j-1] // in this case, a* counts as single a // or dp[i][j] = dp[i][j-2] // in this case, a* counts as empty class Solution { //s 表明文本,p 表明模式 public boolean isMatch(String s, String p) { //二者中有一個是空字符串,不包括全是空字符串的狀況 if(s==null||p==null){ return false; } int sl=s.length(); int pl=p.length(); boolean[][] dp=new boolean[sl+1][pl+1]; //這裏有什麼做用,爲何要這樣寫 dp[0][0]=true; //dp[i][j] 表明文本第i個字符,模式第j個字符以前的子字符串都是匹配的*** //建立的 boolean 默認應該是 false 吧 for(int i=0;i<pl;i++){ if(p.charAt(i)=='*'&&dp[0][i-1]){ dp[0][i+1]=true; } } for(int i=0;i<sl;i++){ for(int j=0;j<pl;j++){ if(p.charAt(j)=='.'){ //前面的匹配結果會對後面的匹配產生影響 dp[i+1][j+1]=dp[i][j]; } if(p.charAt(j)==s.charAt(i)){ dp[i+1][j+1]=dp[i][j]; } if(p.charAt(j)=='*'){ if(p.charAt(j-1)!=s.charAt(i)&&p.charAt(j-1)!='.'){ dp[i+1][j+1]=dp[i+1][j-1]; }else{ dp[i+1][j+1]=(dp[i+1][j]||dp[i][j+1]||dp[i+1][j-1]); } } } } return dp[sl][pl]; } }
032
//找到知足()組成的有效最長子串,算法應該返回其長度 //兩種解決方法,一種是利用棧的後進先出,一種是使用動態規劃 class Solution { public int longestValidParentheses(String s) { int res=0; Stack<Integer> stack=new Stack<>(); //若是一個這樣的括號都沒有,則返回 -1 stack.push(-1); //對給出的字符串進行遍歷 for(int i=0;i<s.length();i++){ //若是遇到左半括號,將括號的索引入棧 if(s.charAt(i)=='('){ stack.push(i); //若是遇到右半括號,則將棧中元素出棧,以前沒有左括號入棧的話出棧的將是 -1 }else{ stack.pop(); //若是以前沒有左括號入棧,則將如今右括號的索引入棧 if(stack.isEmpty()){ stack.push(i); //若是還有元素,多是 -1 也多是第一個左括號的索引 }else{ //更新結果變量,若是前兩個字符就是知足條件的括號,1-(-1)=2 res=Math.max(res,i-stack.peek()); } } } return res; } } //使用動態規劃的方法 class Solution { public int longestValidParentheses(String s) { //動態規劃, We make use of a \text{dp}dp array where iith element of \text{dp}dp represents the length of the longest valid substring ending at iith index. 遍歷到 i 索引爲止最長的符合要求的子字符串長度 int res=0; int dp[]=new int[s.length()]; //從 1 開始,由於一個符號不可能組成一組括號 for(int i=1;i<s.length();i++){ if(s.charAt(i)==')'){ //這個 if 做用域判斷的是左右括號緊挨着的狀況 if(s.charAt(i-1)=='('){ //把長度存放到 dp 相應的索引位置,方便之後的判斷進行使用 //索引爲 1 時不可能組成一對括號,向前數兩個索引位置的長度加上當前一堆括號的長度 dp[i]=(i>=2?dp[i-2]:0)+2; //i-dp[i-1]-1 表明的是出去前面緊挨着的括號的長度,找到再前面的一個符號的位置 }else if(i-dp[i-1]>0&&s.charAt(i-dp[i-1]-1)=='('){ // dp[i]=dp[i-1]+((i-dp[i-1])>=2?dp[i-dp[i-1]-2]:0)+2; } res=Math.max(res,dp[i]); } } return res; } }
044
//進行正則表達式的'?' and '*'匹配判斷 //?表明任何單個字符 class Solution { public boolean isMatch(String s, String p) { int sl=s.length(); int pl=p.length(); boolean[][] dp=new boolean[sl+1][pl+1]; //dp 網格從後向前遍歷 dp[sl][pl]=true; for(int i=pl-1;i>=0;i--){ if(p.charAt(i)!='*'){ break; }else{ dp[sl][i]=true; } } for(int i=sl-1;i>=0;i--){ for(int j=pl-1;j>=0;j--){ if(s.charAt(i)==p.charAt(j)||p.charAt(j)=='?'){ dp[i][j]=dp[i+1][j+1]; }else if(p.charAt(j)=='*'){ dp[i][j]=dp[i+1][j]||dp[i][j+1]; }else{ dp[i][j]=false; } } } return dp[0][0]; } }
070
//爬樓梯問題,每次只能爬1步或者2步,有幾種途徑能夠爬到指定的數字高度 //這也是一個斐波那契數的問題 //由於經過觀察能夠發現這個問題能夠分解成子問題 //dp[i]=dp[i−1]+dp[i−2] class Solution { public int climbStairs(int n) { //由於從上一步跳到最後的結果位置,有兩種可能,一是從上次一步跳過來,一種是從上次一次跳兩步跳過來 //沒有這個判斷會出現數組越界異常 if(n==1){ return 1; } int[] dp=new int[n+1]; dp[1]=1; dp[2]=2; //所謂動態規劃,就是根據公式對 dp 進行不一樣的操做 for(int i=3;i<=n;i++){ dp[i]=dp[i-1]+dp[i-2]; } return dp[n]; } }
072***
//給定兩個單詞,從前一個單詞轉換到後一個詞所用的最小步驟,每步只能進行插入,刪除,或者替換一個字符的操做 //使用 dp[i][j] 表明從 word1[0,i) and word2[0, j) 的最小距離 // Then compare the last character of word1[0,i) and word2[0,j), which are c and d respectively (c == word1[i-1], d == word2[j-1]): // if c == d, then : f[i][j] = f[i-1][j-1] // Otherwise we can use three operations to convert word1 to word2: // (a) if we replaced c with d: f[i][j] = f[i-1][j-1] + 1; // (b) if we added d after c: f[i][j] = f[i][j-1] + 1; // (c) if we deleted c: f[i][j] = f[i-1][j] + 1; class Solution { public int minDistance(String word1, String word2) { int l1=word1.length(); int l2=word2.length(); int[][] dp=new int[l1+1][l2+1]; // Base Case: f(0, k) = f(k, 0) = k for(int i=0;i<=l1;i++){ dp[i][0]=i; } for(int i=1;i<=l2;i++){ dp[0][i]=i; } //對 dp 進行記憶賦值 for(int i=0;i<l1;i++){ for(int j=0;j<l2;j++){ //相應位置處的字符相同時,和以前進行操做的步驟相同 if(word1.charAt(i)==word2.charAt(j)){ dp[i+1][j+1]=dp[i][j]; }else{ //須要進行代替時的步驟 int a=dp[i][j]; //須要刪除字符時的步驟 int b=dp[i][j+1]; //須要插入字符時的步驟 int c=dp[i+1][j]; //將三者中的最小值給下一個 dp 元素 dp[i+1][j+1]=a<b?(a<c?a:c):(b<c?b:c); dp[i+1][j+1]++; } } } return dp[l1][l2]; } }
091
//解碼方式:26 個字母由1-26進行編碼,給定一個數字,判斷可能由幾種解碼方式,好比26可能表明的是Z,也多是BF兩個字母 class Solution { public int numDecodings(String s) { int n=s.length(); if(n==0){ return 0; } int[] dp=new int[n+1]; //肯定基本狀況的過程 //最後一個數字在後面的數字默認設置爲一種解碼方式 dp[n]=1; //n-1 表明的是最後一個位置的數字,當其爲 0 時沒有解碼方式,爲 1 時有一種解碼方式 dp[n-1]=s.charAt(n-1)!='0'?1:0; //製做記憶字典的過程 for(int i=n-2;i>=0;i--){ //0 須要單獨考慮,當在字符串中間遇到 0 時,進入下一次循環 if(s.charAt(i)=='0'){ continue; }else{ //從數字的最後一個位置開始進行記錄 //判斷兩個字符位置內組成的數字是否大於26 //由於輸入的是字符串,須要將字符串轉化成數字進行比較 dp[i]=(Integer.parseInt(s.substring(i,i+2))<=26)?dp[i+1]+dp[i+2]:dp[i+1]; } } return dp[0]; } }
097
//交錯字符串:判斷給定的三個字符串中,第三個字符串是不是前兩個字符串中的字符交錯組成 class Solution { public boolean isInterleave(String s1, String s2, String s3) { int l1=s1.length(); int l2=s2.length(); int l3=s3.length(); //當前兩個字符串的長度和不等於第三個字符串的長度時,直接返回 false if(l3!=l1+l2){ return false; } boolean[][] dp=new boolean[l1+1][l2+1]; for(int i=0;i<=l1;i++){ for(int j=0;j<=l2;j++){ if(i==0&&j==0){ dp[i][j]=true; }else if(i==0){ dp[i][j]=dp[i][j-1]&&s2.charAt(j-1)==s3.charAt(i+j-1); }else if(j==0){ dp[i][j]=dp[i-1][j]&&s1.charAt(i-1)==s3.charAt(i+j-1); }else{ dp[i][j]=(dp[i-1][j]&&s1.charAt(i-1)==s3.charAt(i+j-1))||(dp[i][j-1]&&s2.charAt(j-1)==s3.charAt(i+j-1)); } } } return dp[l1][l2]; } }
115
//給定兩個字符串,判斷從其中一個字符串選出一段序列後等於另外一個字符串的數量 // The idea is the following: // we will build an array mem where mem[i+1][j+1] means that S[0..j] contains T[0..i] that many times as distinct subsequences. Therefor the result will be mem[T.length()][S.length()]. // we can build this array rows-by-rows: // the first row must be filled with 1. That's because the empty string is a subsequence of any string but only 1 time. So mem[0][j] = 1 for every j. So with this we not only make our lives easier, but we also return correct value if T is an empty string. // the first column of every rows except the first must be 0. This is because an empty string cannot contain a non-empty string as a substring -- the very first item of the array: mem[0][0] = 1, because an empty string contains the empty string 1 time. // So the matrix looks like this: // S 0123....j // T +----------+ // |1111111111| // 0 |0 | // 1 |0 | // 2 |0 | // . |0 | // . |0 | // i |0 | // From here we can easily fill the whole grid: for each (x, y), we check if S[x] == T[y] we add the previous item and the previous item in the previous row, otherwise we copy the previous item in the same row. The reason is simple: // if the current character in S doesn't equal to current character T, then we have the same number of distinct subsequences as we had without the new character. // if the current character in S equal to the current character T, then the distinct number of subsequences: the number we had before plus the distinct number of subsequences we had with less longer T and less longer S. // An example: // S: [acdabefbc] and T: [ab] // first we check with a: // * * // S = [acdabefbc] // mem[1] = [0111222222] // then we check with ab: // * * ] // S = [acdabefbc] // mem[1] = [0111222222] // mem[2] = [0000022244] // And the result is 4, as the distinct subsequences are: // S = [a b ] // S = [a b ] // S = [ ab ] // S = [ a b ] // It was really hard to find the reason. Hope the following makes sense. // lets see if T[i] != S[j] // then we stil have to find the entire T in a subset of S[j-1] hence it will be listed as // dp[i,j] = dp[i,j-1] // i.e. ignore the jth character in S. // now if T[i] == S[j] it means we have a choice, either we take the jth character to find the entire T or we do not take the jth character to find the entire T. // If we take the jth character - that means now we have to find a solution to T[i-1] (because T[i] == S[j])) // If we do not take the jth character - that means now we have to find a solution to T[i] from S[j-1] (not taking the jth character). // The total number of permutations would be = permutations with considering the jth character + permutations without considering the jth character. // Hence in this case dp[i,j] = dp[i-1,j-1] + dp[i,j-1]. // Hope that make sense. class Solution { public int numDistinct(String s, String t) { int[][] dp=new int[t.length()+1][s.length()+1]; for(int j=0;j<=s.length();j++){ dp[0][j]=1; } for(int i=0;i<t.length();i++){ for(int j=0;j<s.length();j++){ if(t.charAt(i)==s.charAt(j)){ dp[i+1][j+1]=dp[i][j]+dp[i+1][j]; }else{ dp[i+1][j+1]=dp[i+1][j]; } } } return dp[t.length()][s.length()]; } }
132
//迴文切分:給定一個字符串,使得對該字符串進行幾回分割後,每一個子字符串都是迴文字符串,求進行分割的最小次數並返回 //先畫出網格根據一個通常的字符串總結出規律,在列出僞代碼,最後根據僞代碼寫出相應的可運行的代碼 // This can be solved by two points: // cut[i] is the minimum of cut[j - 1] + 1 (j <= i), if [j, i] is palindrome. // If [j, i] is palindrome, [j + 1, i - 1] is palindrome, and c[j] == c[i]. // The 2nd point reminds us of using dp (caching). // a b a | c c // j i // j-1 | [j, i] is palindrome // cut(j-1) + 1 class Solution { public int minCut(String s) { int n=s.length(); //從 0 到 i 位置處表明須要切割機次才能完成目標 int[] cut=new int[n]; //從 j 到 i 位置的子字符串是不是迴文的 boolean[][] dp=new boolean[n][n]; for(int i=0;i<n;i++){ //最小的切分次數變量,初始化爲切分紅單個字符 int min=i; for(int j=0;j<=i;j++){ //從j+1到i-1不包含字符 if(s.charAt(j)==s.charAt(i)&&(j+1>i-1||dp[j+1][i-1])){ dp[j][i]=true; min=j==0?0:Math.min(min,cut[j-1]+1); } } cut[i]=min; } return cut[n-1]; } }
139
//單詞分割:給定一個非空字符串和一個由子字符串組成的非空字符串列表,判斷該字符串可否被分割成列表中的單詞,前提是列表中的字符串能夠屢次使用,可是列表中不能出現重複字符串 class Solution { public boolean wordBreak(String s, List<String> wordDict) { boolean[] dp=new boolean[s.length()+1]; dp[0]=true; for(int i=1;i<=s.length();i++){ for(int j=0;j<i;j++){ if(dp[j]&&wordDict.contains(s.substring(j,i))){ dp[i]=true; //不知道有什麼用處 break; } } } return dp[s.length()]; } }
140
//單詞分割:返回全部可能的組成字符串的子字符串集合,前一題只是判斷是否能分割,這一題須要返回分割成的 //一種帶記憶的遞歸解決方法 class Solution { //經過一個 HashMap 創建緩存,緩存已經遍歷過的子字符串 private final Map<String,List<String>> cache=new HashMap<>(); public List<String> wordBreak(String s, List<String> wordDict) { if(cache.containsKey(s)){ return cache.get(s); } List<String> res=new LinkedList<>(); if(wordDict.contains(s)){ res.add(s); } for(int i=1;i<s.length();i++){ String left=s.substring(0,i),right=s.substring(i); if(wordDict.contains(left)&&containsSuffix(right,wordDict)){ for(String str:wordBreak(right,wordDict)){ res.add(left+" "+str); } } } cache.put(s,res); return res; } //經過對後綴的預判斷直接排除後綴不一樣的字符串 private boolean containsSuffix(String str,List<String> wordDict){ for(int i=0;i<str.length();i++){ if(wordDict.contains(str.substring(i))){ return true; } } return false; } }
174
//地牢遊戲:九宮格表明不一樣的房間,須要騎士穿過房間從左上角到達右下角,求騎士須要的最小的生命值才能救到公主 class Solution { public int calculateMinimumHP(int[][] dungeon) { if(dungeon==null||dungeon.length==0||dungeon[0].length==0){ return 0; } //行數和列數 int m=dungeon.length; int n=dungeon[0].length; int[][] health=new int[m][n]; health[m-1][n-1]=Math.max(1-dungeon[m-1][n-1],1); for(int i=m-2;i>=0;i--){ health[i][n-1]=Math.max(health[i+1][n-1]-dungeon[i][n-1],1); } for(int j=n-2;j>=0;j--){ health[m-1][j]=Math.max(health[m-1][j+1]-dungeon[m-1][j],1); } for(int i=m-2;i>=0;i--){ for(int j=n-2;j>=0;j--){ int down=Math.max(health[i+1][j]-dungeon[i][j],1); int right=Math.max(health[i][j+1]-dungeon[i][j],1); health[i][j]=Math.min(right,down); } } return health[0][0]; } }
188
//買賣股票的最佳時機:找出能夠獲取的最大利潤,最多能夠完成 k 筆交易 class Solution { public int maxProfit(int k, int[] prices) { int len=prices.length; if(k>=len/2){ return quickSolve(prices); } int[][] t=new int[k+1][len]; for(int i=1;i<=k;i++){ int tempMax=-prices[0]; for(int j=1;j<len;j++){ t[i][j]=Math.max(t[i][j-1],prices[j]+tempMax); tempMax=Math.max(tempMax,t[i-1][j-1]-prices[j]); } } return t[k][len-1]; } //處理角落問題的輔助方法 private int quickSolve(int[] prices){ int len=prices.length,profit=0; for(int i=1;i<len;i++){ if(prices[i]>prices[i-1]){ profit+=prices[i]-prices[i-1]; } } return profit; } }
198
//房屋搶劫:給定一個數組,數組中的值表明房間中金錢的數目,不能搶劫連續的臨近的房屋,不然就會被警察發現,求可以搶劫到的最大金額 //算法不太可能開始就給出一個完美的解決方案,除非是以前解決過的問題,須要提出一個基本可能的算法後,在實際中逐漸的改進算法的性能 //重點知識:解決算法問題的通常方法: // 一、找到遞歸關係 // 二、自頂向下的進行遞歸 // 三、帶有記憶的自頂向下的遞歸也是一種 dp 方法 // 四、帶有記憶的自底向上的迭代 // 五、具備多個變量的自底向上的迭代 //搶劫時候,面臨兩種選擇,搶目前房間和不搶目前房間 rob(i) = Math.max( rob(i - 2) + currentHouseValue, rob(i - 1) ) //特別好的答案解釋:https://leetcode.com/problems/house-robber/discuss/156523/From-good-to-great.-How-to-approach-most-of-DP-problems. //Recursive (top-down) class Solution { public int rob(int[] nums) { //從後向前開始遍歷整個數組 //自頂下下就體如今這裏 return rob(nums,nums.length-1); } private int rob(int[] nums,int i){ if(i<0){ return 0; } return Math.max(rob(nums,i-2)+nums[i],rob(nums,i-1)); } } //Recursive + memo (top-down) class Solution { //定義一個存放記憶的數組變量 int[] memo; public int rob(int[] nums) { memo=new int[nums.length+1]; //初始化時數組,將指定元素填充滿整個數組 Arrays.fill(memo,-1); return rob(nums,nums.length-1); } private int rob(int[] nums,int i){ if(i<0){ return 0; } //memo 中的值大於 0 時說明以前已經計算過當前索引的總和直接返回就好 if(memo[i]>=0){ return memo[i]; } int res=Math.max(nums[i]+rob(nums,i-2),rob(nums,i-1)); memo[i]=res; return res; } } //Iterative + memo (bottom-up) class Solution { public int rob(int[] nums) { //迭代的帶記憶方法,也就是dp if(nums.length==0){ return 0; } //這裏的 memo 就是至關於 dp 數組 int[] memo=new int[nums.length+1]; //爲何第一個元素初始化爲 0 memo[0]=0; memo[1]=nums[0]; for(int i=1;i<nums.length;i++){ //經過一個變量來保存當前的索引位置處的值 int val=nums[i]; memo[i+1]=Math.max(memo[i],memo[i-1]+val); } return memo[nums.length]; } } //Iterative + 2 variables (bottom-up) class Solution { public int rob(int[] nums) { if(nums.length==0){ return 0; } //再上一步中咱們只是用到了memo[i] and memo[i-1];兩個值,能夠設置兩個變量來保存它們 //表明的是nums[i] int prev1=0; //表明的是nums[i-1] int prev2=0; for(int num:nums){ //怎麼從上一步的代碼改爲這種由變量表示的形式是問題解決的關鍵 int temp=prev1; prev1=Math.max(prev2+num,prev1); prev2=temp; } return prev1; } }
213
//搶劫房屋:和以前的不一樣是全部的 class Solution { public int rob(int[] nums) { if(nums.length==1){ return nums[0]; } return Math.max(rob(nums,0,nums.length-2),rob(nums,1,nums.length-1)); } //掠奪指定範圍內的房屋的輔助方法 private int rob(int[] nums,int lo,int hi){ int prev1=0; int prev2=0; for(int i=lo;i<=hi;i++){ int tempPrev1=prev1; int tempPrev2=prev2; prev1=tempPrev2+nums[i]; prev2=Math.max(tempPrev1,tempPrev2); } return Math.max(prev1,prev2); } }
221
//給定一個只有0和1組成的二維矩陣,返回其中只包含1的正方形的面積 //使用動態規劃的方法, // 一、首先將整個dp矩陣初始化爲全0 // 二、dp[i][j]表明右下角是(i,j)的最大正方形的邊長 // 三、從(0,0)索引處開始,沒找到一個1元素就更新dp當前的值 dp(i,j)=min(dp(i−1,j),dp(i−1,j−1),dp(i,j−1))+1 // 四、這樣只要遍歷一次二維矩陣就能得到符合要求的最大面積 class Solution { public int maximalSquare(char[][] matrix) { if(matrix==null||matrix.length==0||matrix[0].length==0){ return 0; } int m=matrix.length,n=matrix[0].length; int[][] dp=new int[m+1][n+1]; //最大知足要求的子矩陣的邊長 int res=0; for(int i=1;i<=m;i++){ for(int j=1;j<=n;j++){ //爲何選擇(i-1,j-1)這個座標進行比價呢? if(matrix[i-1][j-1]=='1'){ dp[i][j]=Math.min(Math.min(dp[i][j-1],dp[i-1][j]),dp[i-1][j-1])+1; res=Math.max(res,dp[i][j]); } } } return res*res; } }
264
//醜數擴展題:給定一個索引,找出位於指定位置的醜數並返回其值,一個醜數的指數因子只能是2,3,5 //經過不停的除以這些指數因子,最後餘數爲1的就是醜數了 // (1) 1×2, 2×2, 3×2, 4×2, 5×2, … // (2) 1×3, 2×3, 3×3, 4×3, 5×3, … // (3) 1×5, 2×5, 3×5, 4×5, 5×5, … // 採用相似於合併的方法,每次從三個序列中選擇較小的那個組曾一個新的序列,而後向後移動一位 class Solution { public int nthUglyNumber(int n) { //實際給的索引是從1開始數起的,而數組中的索引是從0開始數起的 int[] dp=new int[n]; dp[0]=1; int indexOf2=0,indexOf3=0,indexOf5=0; int factorOf2=2,factorOf3=3,factorOf5=5; for(int i=1;i<n;i++){ int min=Math.min(Math.min(factorOf2,factorOf3),factorOf5); dp[i]=min; if(factorOf2==min){ factorOf2=2*dp[++indexOf2]; } if(factorOf3==min){ factorOf3=3*dp[++indexOf3]; } if(factorOf5==min){ factorOf5=5*dp[++indexOf5]; } } return dp[n-1]; } }
279
//最好的正方數:給定一個正整數,找出總和爲這個數的由最正方數組成的最少的正方數個數 // dp[0] = 0 // dp[1] = dp[0]+1 = 1 // dp[2] = dp[1]+1 = 2 // dp[3] = dp[2]+1 = 3 // dp[4] = Min{ dp[4-1*1]+1, dp[4-2*2]+1 } // = Min{ dp[3]+1, dp[0]+1 } // = 1 // dp[5] = Min{ dp[5-1*1]+1, dp[5-2*2]+1 } // = Min{ dp[4]+1, dp[1]+1 } // = 2 // . // . // . // dp[13] = Min{ dp[13-1*1]+1, dp[13-2*2]+1, dp[13-3*3]+1 } // = Min{ dp[12]+1, dp[9]+1, dp[4]+1 } // = 2 // . // . // . // dp[n] = Min{ dp[n - i*i] + 1 }, n - i*i >=0 && i >= 1 class Solution { public int numSquares(int n) { int[] dp=new int[n+1]; //將dp數組用最大的整數填充 Arrays.fill(dp,Integer.MAX_VALUE); dp[0]=0; for(int i=1;i<=n;i++){ int min=Integer.MAX_VALUE; int j=1; while(i-j*j>=0){ min=Math.min(min,dp[i-j*j]+1); j++; } dp[i]=min; } return dp[n]; } }
300
//最長的遞增的子序列:時間複雜度要求On2或者nlogn,可能含有多個自小長度的子序列,可是隻要返回其長度就好 class Solution { public int lengthOfLIS(int[] nums) { if(nums.length==0){ return 0; } //構造dp數組並初始化第一個元素 int[] dp=new int[nums.length]; dp[0]=1; //存放結果的變量,若是數組單調遞,則結果爲1 int maxans=1; //只有在判斷條件中用到了nums數組,其餘操做都是對dp數組進行的 for(int i=1;i<nums.length;i++){ //i索引每向前滾動一位,臨時存放長度的值就得變化一次 int maxval=0; for(int j=0;j<i;j++){ //當i位置前的元素含有比i位置處元素小的時候,取以前保存的最長遞增值的狀態 if(nums[i]>nums[j]){ maxval=Math.max(maxval,dp[j]); } } dp[i]=maxval+1; maxans=Math.max(maxans,dp[i]); } return maxans; } }
003
//最長不含重複的子串:給定一個字符串,找出最長的不含有重複字符的子串 //一種比較好的方法是利用滑動窗口 class Solution { public int lengthOfLongestSubstring(String s) { int res=0; for(int i=0;i<s.length();i++){ //爲何這裏用等於號? for(int j=i+1;j<=s.length();j++){ //若是子字符串知足都不相同的條件,則可能進行答案的替換 if(helper(s,i,j)){ res=Math.max(res,j-i); } } } return res; } //輔助方法判斷從 i 到 j 的子字符串是否全不相同 private boolean helper(String s,int start,int end){ //使用 Set 數據結構進行是否包含的判斷 Set<Character> set=new HashSet<>(); for(int i=start;i<end;i++){ //取出當前位置處的字符 Character ch=s.charAt(i); if(set.contains(ch)){ return false; } set.add(ch); } return true; } } class Solution { public int lengthOfLongestSubstring(String s) { //咱們重複檢查一些子字符串,當檢查過的一個子字符串不包含重複字符時,下一次只需檢查下一個字符就行 int res=0; Set<Character> set=new HashSet<>(); //窗口的左右端點 int lo=0,hi=0; //兩個指針都不超過範圍,就繼續循環 while(lo<s.length()&&hi<s.length()){ //當窗口中不含有右端點字符時,右端點向後移動一個位置 if(!set.contains(s.charAt(hi))){ set.add(s.charAt(hi++)); //當前的符合要求的子字符串與最大的長度比較 res=Math.max(res,hi-lo); } //包含的話就左端口向有滑動,就是刪除左端點的字符,直到不包含爲止 else{ //不在進行添加右指針的操做 set.remove(s.charAt(lo++)); } } return res; } }
006
//之字型排列:一個字符串,其中字符是按照一行的順序給出的,給定一個行數,將這個字符串中的全部字符按之字形排列後,從左到右逐行讀出 class Solution { public String convert(String s, int numRows) { if(numRows==1){ return s; } //定義結果集合,其中存放的是 StringBuilder 結構 List<StringBuilder> rows=new ArrayList<>(); //有幾行就將集合中元素初始化爲幾段 //注意當總的字符數小於行數的狀況 //先把各行肯定並添加進去 for(int i=0;i<Math.min(numRows,s.length());i++){ rows.add(new StringBuilder()); } //聲明並初始化當前的行,而且定義一個標識符表明是否向下移動字符串 int currRow=0; boolean goingDown=false; //數組能夠用 foreach 遍歷,將字符串轉化成字符數組 for(Character ch:s.toCharArray()){ rows.get(currRow).append(ch); //達到拐的時候進行轉向處理 if(currRow==0||currRow==numRows-1){ goingDown=!goingDown; } //針對目前行來講根據轉向的狀況判斷加一仍是減一 currRow+=goingDown?1:-1; } //將集合中的字符串拼接成一個字符串,使用 Builder 注意將其轉化成字符串 //不是集合不帶泛型 <> 的結構 StringBuilder res=new StringBuilder(); //注意對集合中的元素進行取出並操做的時候的方法 for(StringBuilder row:rows){ res.append(row); } return res.toString(); } }
008
//字符串轉換成數字:實現一個函數,將字符串轉換成數字 //須要考慮四種狀況: // 一、忽略開始的空字符 // 二、若是字符串中含有數字怎麼處理 // 三、溢出,輸入是空字符串或者只有正號或負號的時候 // 四、非法輸入,第一個非空字符必須是數字或者加減號纔是合法的輸入 //經過設置全局變量來對返回時是 0 仍是非法輸入進行判斷??代碼怎麼寫 class Solution { public int myAtoi(String str) { //去掉全部的前置空格進行判斷 if (str.trim().isEmpty()){ return 0; } //sign 至關因而正負號的標誌 //base 表明每一位的數字 int sign=1,base=0,i=0; //對前置空格進行處理 while(str.charAt(i)==' '){ i++; } //對輸入的加減號進行處理 if(str.charAt(i)=='-'||str.charAt(i)=='+'){ //負號時將標識符變成負一,正號時標識符不變 //這裏忘記 i++ 了致使出現+或-號時出錯 sign=1-2*((str.charAt(i++)=='-')?1:0); } //輸入的字符串是數字的時候 while(i<str.length()&&str.charAt(i)>='0'&&str.charAt(i)<='9'){ //由於整數的最大最小值的最後一位是8,須要判斷輸入的最後一位是否是8或者9 if(base>Integer.MAX_VALUE/10||(base==Integer.MAX_VALUE/10&&(str.charAt(i)-'0'>7))){ //兩層判斷條件 if(sign==1){ return Integer.MAX_VALUE; }else{ return Integer.MIN_VALUE; } } base=10*base+(str.charAt(i++)-'0'); } return sign*base; } }
012
//整數轉換成羅馬數字:比較特殊的羅馬數字是1,5,10,50,100,500,1000還有這些數字像前面輸一個單位的數字 class Solution { public String intToRoman(int num) { //爲何從大到小順序排列,由於判斷後進行操做的時候使用的是減法 int[] values={1000,900,500,400,100,90,50,40,10,9,5,4,1}; String[] strs={"M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"}; StringBuilder res=new StringBuilder(); for(int i=0;i<values.length;i++){ //用循環,依次遞加 //等於號不能丟 while(num>=values[i]){ num-=values[i]; //兩個字符串的索引是一一對應的,能夠共用一套 res.append(strs[i]); } } return res.toString(); } }
013
//羅馬數字轉換成阿拉伯數字:和上一題正好相反 class Solution { public int romanToInt(String s) { //使用 Map 存儲羅馬數字和阿拉伯數字的映射關係 if(s==null||s.length()==0){ return -1; } Map<Character,Integer> map=new HashMap<>(); //添加映射關係 map.put('I',1); map.put('V',5); map.put('X',10); map.put('L',50); map.put('C',100); map.put('D',500); map.put('M',1000); int len=s.length(),res=map.get(s.charAt(len-1)); //若是羅馬數字只佔一個字符的位置,直接按照字典中的值取出阿拉伯數字便可,不然進入循環 for(int i=len-2;i>=0;i--){ //對於像4,9這種元素要特殊處理,方法就是判斷是否當前字母大於或等於後一個字母 if(map.get(s.charAt(i))>=map.get(s.charAt(i+1))){ res+=map.get(s.charAt(i)); //此時說明是須要特殊處理的元素 }else{ res-=map.get(s.charAt(i)); } } return res; } }
017
//手機鍵盤的字母組合:給出2-9中數字組成的一個數字,返回全部由其表明的字符組合成的字符串的集合 //回溯 class Solution { public List<String> letterCombinations(String digits) { List<String> res=new ArrayList<>(); if(digits==null||digits.length()==0){ return res; } //使用 Map 數據結構存放字典 Map<String,String> map=new HashMap<>(); map.put("2","abc"); map.put("3","def"); map.put("4","ghi"); map.put("5","jkl"); map.put("6","mno"); map.put("7","pqrs"); map.put("8","tuv"); map.put("9","wxyz"); //""==new String() backtrack("",digits,map,res); return res; } private void backtrack(String combination,String next,Map<String,String> map,List<String> res){ //若是沒有數字能夠檢查,說明添加的當前這個字符串完成 if(next.length()==0){ res.add(combination); }else{ String digit=next.substring(0,1); String letters=map.get(digit); //便利添加一個數字對應的字母 for(int i=0;i<letters.length();i++){ //向後挪一個字母的位置 String letter=map.get(digit).substring(i,i+1); backtrack(combination+letter,next.substring(1),map,res); } } } }
020
//合法的括號:判斷小括號或者中括號或者大括號是不是合法的成對出現,是經過字符的形式表示 class Solution { public boolean isValid(String s) { Map<Character,Character> map=new HashMap<>(); //將配對括號存入字典中 map.put(')','('); map.put('}','{'); map.put(']','['); //先聲明暫時存放數據的棧 Stack<Character> stack=new Stack<>(); for(int i=0;i<s.length();i++){ char currCh=s.charAt(i); //map中包含當前符號的話,判斷是不是三種括號中的一個 if(map.containsKey(currCh)){ //獲得棧頂的元素,若是爲空,設置虛擬的字符 char topCh=stack.isEmpty()?'#':stack.pop(); //棧頂的元素不等於相應的map中值的話,返回錯誤 if(topCh!=map.get(currCh)){ return false; } //不包含的字符也要入棧方便下一步的判斷 }else{ stack.push(currCh); } } //若是臨時棧爲空,說明全部括號都進行了配對 return stack.isEmpty(); } }
022
//生成括號:生成指定個數的全部可能的配對括號,制定個數,返回括號的集合 class Solution { public List<String> generateParenthesis(int n) { //此類問題直接想到回溯 List<String> res=new ArrayList<>(); backtrack(0,0,res,new String(),n); return res; } private void backtrack(int start,int end,List<String> res,String tempStr,int n){ //結束條件是第一個關鍵點,當前字符串的長度等於最大半括號數目的二倍時返回 if(tempStr.length()==n*2){ res.add(tempStr); return; } //先將作左半括號添加,在遞歸的返回過程當中會進行回溯 if(start<n){ backtrack(start+1,end,res,tempStr+"(",n); } if(end<start){ backtrack(start,end+1,res,tempStr+")",n); } } }
028
//實現返回子字符串索引的方法: 看字符串中是否包含指定的子字符串,不包含就返回 -1 ,包含的話返回其第一個字符所在的索引 class Solution { public int strStr(String haystack, String needle) { int l1=haystack.length(),l2=needle.length(); //當子字符串的長度長於主字符串時或者等於零時 if(l2>l1){ return -1; } if(l2==0){ return 0; } //只須要判斷去掉子字符串長度後的剩餘長度 int len=l1-l2; for(int i=0;i<=len;i++){ //注意加上的子字符串長度爲 l2 的長度 if(haystack.substring(i,i+l2).equals(needle)){ return i; } } return -1; } }
030不會
//給定一個字符串和一個由相同長度組成的字符串列表,找出字符串中只包含列表中單詞的開始索引 class Solution { public List<Integer> findSubstring(String s, String[] words) { int N = s.length(); List<Integer> indexes = new ArrayList<Integer>(s.length()); if (words.length == 0) { return indexes; } int M = words[0].length(); if (N < M * words.length) { return indexes; } int last = N - M + 1; //map each string in words array to some index and compute target counters Map<String, Integer> mapping = new HashMap<String, Integer>(words.length); int [][] table = new int[2][words.length]; int failures = 0, index = 0; for (int i = 0; i < words.length; ++i) { Integer mapped = mapping.get(words[i]); if (mapped == null) { ++failures; mapping.put(words[i], index); mapped = index++; } ++table[0][mapped]; } //find all occurrences at string S and map them to their current integer, -1 means no such string is in words array int [] smapping = new int[last]; for (int i = 0; i < last; ++i) { String section = s.substring(i, i + M); Integer mapped = mapping.get(section); if (mapped == null) { smapping[i] = -1; } else { smapping[i] = mapped; } } //fix the number of linear scans for (int i = 0; i < M; ++i) { //reset scan variables int currentFailures = failures; //number of current mismatches int left = i, right = i; Arrays.fill(table[1], 0); //here, simple solve the minimum-window-substring problem while (right < last) { while (currentFailures > 0 && right < last) { int target = smapping[right]; if (target != -1 && ++table[1][target] == table[0][target]) { --currentFailures; } right += M; } while (currentFailures == 0 && left < right) { int target = smapping[left]; if (target != -1 && --table[1][target] == table[0][target] - 1) { int length = right - left; //instead of checking every window, we know exactly the length we want if ((length / M) == words.length) { indexes.add(left); } ++currentFailures; } left += M; } } } return indexes; } }
038
//將給定的一個數字按照指定的規律轉化成一個序列,111221表明的是一個1,一個2,兩個1組成了5,第五行讀的是第四行,要想獲得第六行就得根據第五行的狀況,1出現了三次就是31,2出現了兩次就是22,1出現了一次就是11 class Solution { public String countAndSay(int n) { StringBuilder curr=new StringBuilder("1"); StringBuilder prev; int count; char say; for(int i=1;i<n;i++){ prev=curr; curr=new