日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

力扣刷题系列总结

發布時間:2024/10/5 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 力扣刷题系列总结 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

第一:暴力法

往往是for循環,因為我們做的題目絕大多數都是查找問題。
技巧:下一層for循環從哪里開始查找,從哪里開始停止

//雙層或者多層for循環for (int i = 1; i <= n; i++) {for (int j = i + 1; j <= n; j++) {System.out.println(i+""+j);}

第二:雙指針

技巧:for循環變while,滿足題目中的一些條件時候,while里面中條件用判斷是否跳出循環
一:很典型的是二分查找,這里的條件就是遞增序列
https://leetcode-cn.com/problems/binary-search/

public int search(int[] nums, int target) {int left = 0, right = nums.length - 1;while(left <= right){int mid = left + (right - left) / 2;if(nums[mid] == target) return mid;else if(nums[mid] > target) right = mid - 1;else if(nums[mid] < target) left = mid + 1;}return -1;}

二:很典型的滑動窗口(滑動窗口也屬于雙指針的一種),這里的條件是什么呢?就是和《=target,并且有范圍
自己可以嘗試使用暴力for循環求解一下。
https://leetcode-cn.com/problems/minimum-size-subarray-sum/

public int minSubArrayLen(int target, int[] nums) {int l=0 ,r=0;int count = Integer.MAX_VALUE;while (r<nums.length){target -= nums[r];while (target<=0){count = Math.min(count,r-l+1);target +=nums[l];l++;}r++;}// 注意邊界值的鎖定return count== Integer.MAX_VALUE ? 0 : count;}

三、回溯法

回溯是遞歸的副產品,只要有遞歸就會有回溯。
那么先練習遞歸:遞歸要去理解的話,真的是不好理解
面試題:求x的n次方

int function(int x, int n) {int result = 1; // 注意 任何數的0次方等于1for (int i = 0; i < n; i++) {result = result * x;}return result; }

如果用遞歸實現呢?這個比較好理解,因為每次的返回值都會乘以x,那么累計下來就是x的n次方了

int function(int x, int n) {if (n == 0) {return 1; // return 1 同樣是因為0次方是等于1的}return function(x, n - 1) * x; }

.遞歸就是有去(遞去)有回(歸來)
有去:是指把問題分解成無數的小問題,一層一層地解決,最終到達臨界點之后,即解決完最后一個需要調用自身函數處理的問題之后,有回:將解決的結果原路返回到原點,原問題解決。
實驗一下一下的代碼

static int a;static int function(int x, int n) {if (n == 0) {return 1; // return 1 同樣是因為0次方是等于1的}System.out.println("遞歸前的值"+a);int c = 0;a = function(x, n - 1) * x;c=c+1;System.out.println("c的值"+c);System.out.println("遞歸后a的值"+a);return a;}public static void main(String[] args) {function(3,3);}


你會發現每次遞歸之前a都是0,而當return 1之后,c每次都是1,也就是每次c都是從0開始+1,遞歸后a的值一次是3,9,27.
每次打印a,都是遞的過程,這個過程只產生了一個個子函數的棧,每次打印c都是歸的過程,為什么c的值每次都是從0開始+1呢,因為歸的c,還是曾經那個沒有+1的c,在剛剛結束的函數的棧里面。

組合問題
https://leetcode-cn.com/problems/combinations/
接下里我們來看一個真正的回溯問題:

List<List<Integer>> res=new ArrayList<>();List<Integer> list=new ArrayList<>();public List<List<Integer>> combine(int n, int k) {backTrace( 1,n,k);return res;}void backTrace(int index,int n,int k){if(list.size()==k){res.add(new ArrayList<>(list));return;}for (int i = index; i <= n; i++){list.add(i);backTrace(i+1,n,k);list.remove(list.size()-1);}}

仔細體會一下遞和歸的過程,本地debug一下,特別是第一次return后,從哪里開始執行。

回溯法,一般可以解決如下幾種問題:
組合問題:N個數里面按一定規則找出k個數的集合
切割問題:一個字符串按一定規則有幾種切割方式
子集問題:一個N個數的集合里有多少符合條件的子集
排列問題:N個數按一定規則全排列,有幾種排列方式
棋盤問題:N皇后,解數獨等等
組合是不強調元素順序的,排列是強調元素順序。
回溯算法模板框架如下:

void backtracking(參數) {if (終止條件) {存放結果;return;}for (選擇:本層集合中元素(樹中節點孩子的數量就是集合的大小)) {處理節點;backtracking(路徑,選擇列表); // 遞歸回溯,撤銷處理結果} }

關于回溯法的總結這篇文章已經很地道了:https://programmercarl.com/%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html#%E5%9B%9E%E6%BA%AF%E6%B3%95%E8%A7%A3%E5%86%B3%E7%9A%84%E9%97%AE%E9%A2%98

四、搜索

深度優先搜索

了解遞歸之后,我們要知道遞歸的一種重要的應用算法,深度優先,深度優先一般都是采用遞歸棧的形式來實現
深度優先搜索屬于圖算法的一種,是一個針對圖和樹的遍歷算法,英文縮寫為DFS即Depth First Search。深度優先搜索是圖論中的經典算法,利用深度優先搜索算法可以產生目標圖的相應拓撲排序表,利用拓撲排序表可以方便的解決很多相關的圖論問題,如最大路徑問題等等。一般用堆數據結構來輔助實現DFS算法。其過程簡要來說是對每一個可能的分支路徑深入到不能再深入為止,而且每個節點只能訪問一次。
關于深度優先搜索,這并不是一個很好的例子,因為沒有if()+return,那么什么時候return呢,自然是代碼跑完后return
https://leetcode-cn.com/problems/number-of-provinces/

// n個城市的連通性問題,dfs的深度即省份的個數;這道題由于是nn矩陣那么我們可以按照圖的深度優先搜索來做這道題// 從模版里面看棧里面要存什么?存周圍未訪問的節點 怎么保證已訪問的節點不再訪問?我們用一個boolean數組來保證//if (周邊都沒有相連的) return 換個方向繼續試public int findCircleNum(int[][] isConnected) {int n = isConnected.length;Boolean visited[] = new Boolean[n];int circle = 0;// 每個未訪問的節點都需要進行深搜for (int i = 0; i < n; i++) {if (!visited[i]){circle++; //加次數dfs(isConnected,visited,i,n);}}return circle;}private void dfs(int[][] isConnected,Boolean [] visited, int i, int n) {// 對當前頂點 i 進行訪問標記visited[i] =true;//一個節點與其他的周邊9個節點全部遍歷一遍,這是用圖的方式來做for (int j = 0; j <n ; j++) {if (isConnected[i][j] ==1 && visited[j]!=true){dfs(isConnected,visited,j,n);}//如果沒有就繼續for循環對另外一個做不撞南墻不回頭,如果所有都遍歷完了,那么從這個節點的深度遍歷結束}// 這里其實隱藏著return}

深度優先除了處理圖問題(圖問題又可以用臨接矩陣來代替,本質上是遍歷矩陣)
如果不是nXn矩陣那么純的便利矩陣深度優先搜索又該怎么做呢?那么就變成了深度搜索mXn個單元格,來試試島嶼的個數這道題。
https://leetcode-cn.com/problems/number-of-islands/solution/dao-yu-shu-liang-by-leetcode/

public int numIslands(char[][] grid) {int m =grid.length;int n =grid[0].length;int count = 0;for (int i = 0; i <m ; i++) {for (int j = 0; j < n; j++) {if (grid[i][j]=='1'){count++;dfs(grid,i,j,m,n);}}}return count;}private void dfs(char[][] grid, int i, int j,int m,int n) {if (i < 0 || j < 0 || i >= m || j >= n || grid[i][j] == '0') {return;}grid[i][j] ='0';dfs(grid,i-1,j,m,n);dfs(grid,i+1,j,m,n);dfs(grid,i,j-1,m,n);dfs(grid,i,j+1,m,n);}

還可以處理樹問題,比較典型的先序、中序、后序遍歷
貼一道簡單題:https://leetcode-cn.com/problems/path-sum/

//判斷什么時候終止遞歸?怎么存已經訪問過的節點? 葉子節點終止遞歸,訪問過的我們的tagetsum就會剪掉,算作訪問過了public boolean hasPathSum(TreeNode root, int targetSum) {return dfs(root.right,targetSum);}private boolean dfs(TreeNode root, int targetSum) {if (root==null){return false;}if (root.right==null && root.left==null){return targetSum == root.val;}return dfs(root.right,targetSum-root.val) || dfs(root.left,targetSum-root.val);}

還有就是樹的變形,可以抽象成一顆樹的,比如說字典樹
https://leetcode-cn.com/problems/design-add-and-search-words-data-structure/

class WordDictionary {TrieNode trieTree;public WordDictionary() {trieTree = new TrieNode();}public void addWord(String word) {if (null == word || word.length() < 1){return;}TrieNode cur = trieTree;for (int i = 0; i < word.length(); i++) {if (cur==null){cur.children.put(word.charAt(i),new TrieNode());}if (!cur.children.containsKey(word.charAt(i))){cur.children.put(word.charAt(i),new TrieNode());}cur = cur.children.get(word.charAt(i));}cur.isTail = true;}public boolean search(String word) {TrieNode cur = trieTree;if (cur==null) {return false;}return dfs(cur,0,word);}// 需要解釋下為什么只能有dfs,不能跟add一樣直接將當前置為子節點,因為為.的時候需要全遍歷private boolean dfs(TrieNode trieTree, int index, String word) {if (index>=word.length()){return trieTree.isTail;}if (trieTree.children.containsKey(word.charAt(index))){return dfs(trieTree.children.get(word.charAt(index)),index+1,word);}if (word.charAt(index)=='.'){for (Map.Entry<Character,TrieNode> child:trieTree.children.entrySet()) {if (dfs(child.getValue(),index+1,word)) {return true;}}}return false;}class TrieNode {boolean isTail;HashMap<Character,TrieNode> children;public TrieNode() {children = new HashMap<>();isTail =false;}} }

廣度優先搜索

廣度優先搜索(也稱寬度優先搜索,縮寫BFS,以下采用廣度來描述)是連通圖的一種遍歷算法這一算法也是很多重要的圖的算法的原型。Dijkstra單源最短路徑算法和Prim最小生成樹算法都采用了和寬度優先搜索類似的思想。其別名又叫BFS,屬于一種盲目搜尋法,目的是系統地展開并檢查圖中的所有節點,以找尋結果。換句話說,它并不考慮結果的可能位置,徹底地搜索整張圖,直到找到結果為止。基本過程,BFS是從根節點開始,沿著樹(圖)的寬度遍歷樹(圖)的節點。如果所有節點均被訪問,則算法中止。一般用隊列數據結構來輔助實現BFS算法。

https://leetcode-cn.com/problems/number-of-islands/

// 來看下我們需要什么?需要一個隊列來保存周圍還未訪問的節點,需要一個數組來保存是否訪問過,這里我們直接設置為0來標示// 訪問過了,方向一共四個public int numIslands(char[][] grid) {int count = 0;for(int i = 0; i < grid.length; i++) {for(int j = 0; j < grid[0].length; j++) {if(grid[i][j] == '1'){bfs(grid, i, j);count++;}}}return count;}private void bfs(char[][] grid, int i, int j) {// 想存一個Integer 就將二維數組轉成一維// 借用一個隊列 queue,判斷隊列首部節點 (i, j) 是否未越界且為 1://若是則置零(刪除島嶼節點),并將此節點上下左右節點 (i+1,j),(i-1,j),(i,j+1),(i,j-1) 加入隊列;//若不是則跳過此節點;Deque<int[]> list = new LinkedList<>();list.add(new int[] { i, j });while(!list.isEmpty()) {int[] cur = list.remove();i = cur[0]; j = cur[1];if(0 <= i && i < grid.length && 0 <= j && j < grid[0].length && grid[i][j] == '1') {grid[i][j] = '0'; //相當于刪除這個關系list.add(new int[] { i + 1, j }); //list.add(new int[] { i - 1, j });list.add(new int[] { i, j + 1 });list.add(new int[] { i, j - 1 });}}}``` 這是圖的,我們再來看看樹的層序遍歷 https://leetcode-cn.com/problems/binary-tree-level-order-traversal/ ```java public class inorderTraversal {static List<List<Integer>> result;public List<List<Integer>> levelOrder(TreeNode root) {Deque<TreeNode> que = new LinkedList<>();que.offer(root);while(!que.isEmpty()) {List<Integer> level = new ArrayList<Integer>(); //記錄每一層的listint currentLevelSize = que.size(); //記錄每一層的節點個數for (int i = 0; i <currentLevelSize ; i++) {TreeNode node = que.poll();level.add(node.val);if (node.right!=null) {que.offer(node.right);}if (node.left!=null) {que.offer(node.left);}}result.add(level);}return result;}}

總結一下:

層序遍歷先寫個
while(que!=null)
{ 記錄一下上一層的個數,這一點很重要
依次出隊
處理下這個節點
并且把這個節點的鄰接節點入隊
}

https://blog.csdn.net/qq_35789269/article/details/118686761

拓撲排序

給定一個包含 nn 個節點的有向圖 GG,我們給出它的節點編號的一種排列,如果滿足:
對于圖 GG 中的任意一條有向邊 (u, v)(u,v),u 在排列中都出現在 v的前面。

來通過一道題來實戰拓撲排序:
https://leetcode-cn.com/problems/course-schedule/
1、判斷是否有環來打破拓撲(對應深度優先搜索算法)
記住這三個標志位對應的狀態
i == 0 : 干凈的,未被 DFS 訪問
i == -1:其他節點啟動的 DFS 訪問過了,路徑沒問題,不需要再訪問了
i == 1 :本節點啟動的 DFS 訪問過了,一旦遇到了也說明有環了

class Solution {public boolean canFinish(int numCourses, int[][] prerequisites) {List<List<Integer>> adjacency = new ArrayList<>();for(int i = 0; i < numCourses; i++)adjacency.add(new ArrayList<>());int[] flags = new int[numCourses];for(int[] cp : prerequisites)adjacency.get(cp[1]).add(cp[0]);for(int i = 0; i < numCourses; i++)if(!dfs(adjacency, flags, i)) return false;return true;}private boolean dfs(List<List<Integer>> adjacency, int[] flags, int i) {if(flags[i] == 1) return false;if(flags[i] == -1) return true;flags[i] = 1;for(Integer j : adjacency.get(i))if(!dfs(adjacency, flags, j)) return false;flags[i] = -1;return true;} }

2、通過入度表來打破(對應廣度優先搜索算法)

class Solution {public boolean canFinish(int numCourses, int[][] prerequisites) {int[] indegrees = new int[numCourses];List<List<Integer>> adjacency = new ArrayList<>();Queue<Integer> queue = new LinkedList<>();for(int i = 0; i < numCourses; i++)adjacency.add(new ArrayList<>());// Get the indegree and adjacency of every course.for(int[] cp : prerequisites) {indegrees[cp[0]]++;adjacency.get(cp[1]).add(cp[0]);}// Get all the courses with the indegree of 0.for(int i = 0; i < numCourses; i++)if(indegrees[i] == 0) queue.add(i);// BFS TopSort.while(!queue.isEmpty()) {int pre = queue.poll();numCourses--;for(int cur : adjacency.get(pre))if(--indegrees[cur] == 0) queue.add(cur);}return numCourses == 0;} }

最短路徑問題

最短路徑用bfs處理是最好的
787. K 站中轉內最便宜的航班
https://leetcode-cn.com/problems/cheapest-flights-within-k-stops/

class Solution {public int findCheapestPrice(int n, int[][] flights, int src, int dst, int k) {List<int[]>[] edges = new List[n];// 從src到i到價格int[] price = new int [n];Deque<int[]> queue = new LinkedList<>();for (int i = 0; i <n ; i++) {edges[i] = new ArrayList<>();price[i] = Integer.MAX_VALUE;}for (int i = 0; i < flights.length; i++) {edges[flights[i][0]].add(new int[]{flights[i][1],flights[i][2]});}price[src] = 0;// 這里設計很重要,必須跟個次數,因為有k到限制,src到i的次數和費用queue.offer(new int[]{src,0,price[src]});while (!queue.isEmpty()) {int [] poll = queue.poll();if (poll[1]>k) break;for(int[] next :edges[poll[0]]){if (next[1]+poll[2]<price[next[0]]){price[next[0]] = next[1]+poll[2];//更新費用queue.add(new int[]{next[0],poll[1]+1,price[next[0]]});}}}return price[dst]==Integer.MAX_VALUE?-1:price[dst];} }

涉及到幾種,邊是否帶權重,是否有關系?
List<int[]>[] edges = new List[n];
List<List> adjacency = new ArrayList<>();
還有就是隊列的設計,要包含些什么信息比較好

五、動態規劃

對于迭代搜索求結果,多少種方法的還有一種常用的方法,那就是動態規劃
動態規劃,英文:Dynamic Programming,簡稱DP,如果某一問題有很多重疊子問題,使用動態規劃是最有效的。所以動態規劃中每一個狀態一定是由上一個狀態推導出來的。
對于動態規劃問題,有人將拆解為如下五步曲,這五步都搞清楚了,才能說把動態規劃真的掌握了!

  • 確定dp數組(dp table)以及下標的含義
  • 確定遞推公式
  • dp數組如何初始化
  • 確定遍歷順序
  • 舉例推導dp數組
    來個最簡單的:
    斐波拉契
    https://leetcode-cn.com/problems/fibonacci-number/solution/fei-bo-na-qi-shu-by-leetcode-solution-o4ze/
  • //這個就很簡單了,都給出來了//dp數組就是f(n),遍歷順序從前到后了// F(0) = 0,F(1) = 1// F(n) = F(n - 1) + F(n - 2)public int fib(int n) {int [] dp = new int[n+1];dp[0] =0;dp[1] = 1;for (int i = 2; i <= n; i++) {dp[i] = dp[i-2]+dp[i-1];}return dp[n];}

    這道題也可以做遞歸,當然也并不需要維護這么長的dp數組的。
    動態規劃要不是從小到大,一次遞推,要么是做選擇max、min等。
    打家劫舍:
    https://leetcode-cn.com/problems/house-robber/

    //第i家偷還是不偷//如果偷dp[i] =dp[i-2]+i//不偷dp[i] =dp[i-1]//最佳就是取他們之間最大的public int rob(int[] nums) {if (nums == null || nums.length == 0) return 0;if (nums.length == 1) return nums[0];int n = nums.length;int dp[] = new int[n+1];dp[0] = nums[0];dp[1] =Math.max(dp[0],nums[1]);for (int i = 2; i <n ; i++) {dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);}return dp[n-1];}

    零錢兌換
    https://leetcode-cn.com/problems/coin-change/

    public static int coinChange(int[] coins, int amount) {int max = Integer.MAX_VALUE;int[] dp = new int[amount + 1];//初始化dp數組為最大值for (int j = 0; j < dp.length; j++) {dp[j] = max;}//當金額為0時需要的硬幣數目為0dp[0] = 0;for (int i = 0; i < coins.length; i++) {//正序遍歷:完全背包每個硬幣可以選擇多次for (int j = coins[i]; j <= amount; j++) {//只有dp[j-coins[i]]不是初始最大值時,該位才有選擇的必要,第一次j=coins[i],肯定是0,j再++//就把缺少coins[i]并且小雨amount的dp[amout]求出來了if (dp[j - coins[i]] != max) {//選擇硬幣數目最小的情況,這里的dp[i]實際上上一輪的最小值,所以這里需要做更新dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);}}}return dp[amount] == max ? -1 : dp[amount];}public static void main(String[] args) {coinChange(new int[]{1,2,3},6);}

    六、前綴和(一般用來求子數組的和或者滿足什么條件的子數組等)

    前綴和有個非常神奇的定理:
    以數組為例
    for循環求完前綴和之后+再雙for循環遍歷前綴和的差值(就是子數組的和)還是以上一道題為例子,體會一下這個過程
    雖然對于上道題,除了二分優化了點(下面注釋掉的那一部分),其實沒有什么優勢

    public int minSubArrayLen(int target, int[] nums) {int n = nums.length;int count = Integer.MAX_VALUE;int sum[] = new int[n+1];// 為了方便計算,令 size = n + 1// sums[0] = 0 意味著前 0 個元素的前綴和為 0// sums[1] = A[0] 前 1 個元素的前綴和為 A[0]// 以此類推,不要小看這個假設,和鏈表設置頭節點一樣,這for (int i = 1; i <= n; i++) {sum[i] = sum[i-1]+nums[i];}for (int i = 1; i <= n ; i++) {int sumMax = target +sum[i-1];// 返回// int bound = Arrays.binarySearch(sum, sumMax); 下面那個for循環可以用這個代替int bound = 0;for (int j = 1; j <=n ; j++) {if (sumMax ==sum[j]){bound = j;}}count = Math.min(count,bound-i+1);}return count == Integer.MAX_VALUE ? 0 : count;}

    前綴和對應有個很相似的思想叫差分思想

    差分思想:
    1)對于原始數組arr[a, b, c, d],其差分數組為:diff[a, b-a, c-b, d-c]
    2)差分數組的前綴和數組 == 原始數組,即:求差分數組的前綴和數組,即可還原回去。[a, a + b-a, a+b-a + c-b, …]
    3)對原始數組的區間增加,可以轉化為對其差分數組的兩點增加( O(n) -> O(1) ):
    假設對arr[i … j]區間每個元素全部增加delta,則等價于:diff[i] += delta,diff[j+1] -= delta
    //對于數組 [1,2,2,4],其差分數組為 [1,1,0,2],
    對于原數組,下標1,2的都加一【1,3,3,4】,其差分數組【1,2,0,1】

    鏈接:https://leetcode-cn.com/problems/corporate-flight-bookings

    public class corpFlightBookings {public int[] corpFlightBookings(int[][] bookings, int n) {int[] nums = new int[n];for (int[] booking : bookings) {//先需要將差分數組d[l]位置加 inc,這樣下標 >= l 位置的元素都獲得inc增量//其次,為了不影響原數組中下標大于 r 的元素,需要在d[r + 1]處減去inc,使得原數組下標 > r 的元素值不變.//對于這段話不理解的//對于數組 [1,2,2,4],其差分數組為 [1,1,0,2],//【1,12,12,4】其差分數組為【1,11,0,-8】nums[booking[0]-1] = booking[2];if (booking[1]<n){nums[booking[1]] -= booking[2];}}for (int i = 0; i < n; i++) {nums[i]+=nums[i-1];}return nums;}

    更多前綴和都題目到這篇文章中看
    https://blog.csdn.net/qq_35789269/article/details/120051335?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164070565316780271591421%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=164070565316780271591421&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogfirst_rank_ecpm_v1~rank_v31_ecpm-2-120051335.nonecase&utm_term=%E5%B7%AE%E5%88%86&spm=1018.2226.3001.4450

    七、單調棧

    通常是一維數組,要尋找任一個元素的右邊或者左邊第一個比自己大或者小的元素的位置,此時我們就要想到可以用單調棧了
    每日溫度
    https://leetcode-cn.com/problems/daily-temperatures/

    // 這里的關鍵是棧里面要存下標public int[] dailyTemperatures(int[] temperatures) {Deque<Integer> stack = new LinkedList<>();int n = temperatures.length;int []result = new int[n];for (int i = 0; i < n; i++) {//int temp0 = temperatures[i];while(!stack.isEmpty() && temp0>temperatures[stack.peek()]){//int temp = ;int prevIndex = stack.pop();result[prevIndex] = i-prevIndex;}//只要沒有大的就繼續入棧stack.push(i);}return result;}

    八、樹

    對于樹,我們得知道如何構建一顆比較常見的樹,比如最常見的完全二叉樹,如何遍歷一棵樹
    https://blog.csdn.net/qq_35789269/article/details/116426945?ops_request_misc=%7B%22request%5Fid%22%3A%22163811878116780271558685%22%2C%22scm%22%3A%2220140713.130102334.pc%5Fblog.%22%7D&request_id=163811878116780271558685&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogfirst_rank_v2~rank_v29-1-116426945.pc_v2_rank_blog_default&utm_term=樹&spm=1018.2226.3001.4450
    首先我們來知道如何從一個數組來生成一顆完全二叉樹
    如果此時給我們一個順序存儲的數組,叫我們構建一顆二叉樹
    給定二叉樹 [3,9,20,null,null,15,7],

    // Definition for a binary tree node.public class TreeNode {int val;TreeNode left;TreeNode right;TreeNode(int x) { val = x; }} public static TreeNode createBT(int[] arr, int i) // 初始時,傳入的i==0 {TreeNode root = null; // 定義根節點if (i >= arr.length) // i >= arr.length 時,表示已經到達了根節點return null;root = new TreeNode(arr[i]); // 根節點// 這里其實很不好理解,理解的關鍵在于i值在遞和歸的時候的變化root.left = createBT(arr, 2*i+1); // 遞歸建立左孩子結點root.right = createBT(arr, 2*i+2); // 遞歸建立右孩子結點return root; }public class Main {public static void main(String[] args) {int[] arr = {3,9,20,null,null,15,7};TreeNode root = createBT(arr, 0);System.out.println("先序遍歷:");PreOrder(root);System.out.println("\n中序遍歷:");InOrder(root);System.out.println("\n后序遍歷:");PostOrder簡單題(root);} // 這里只寫preOrder,中序和后序自己寫下public static void preOrder(TreeNode root){if (root==null){return;}System.out.println(root.val);preOrder(root.left) ;preOrder(root.right);}

    再來處理一道關于樹的題目:
    翻轉二叉樹:
    https://leetcode-cn.com/problems/invert-binary-tree/
    先分析一下:如果只有一層的二叉樹我們會怎么寫代碼?

    TreeNode temp = root.right;
    root.right = root.left;
    root.left = temp;
    我相信每個人應該都會寫出這樣的代碼。
    這個時候我們回想下遞歸,如果我們從根節點開始,遞歸地對樹進行遍歷,并從葉子節點先開始翻轉,那么不是就把整棵樹整完了嗎?
    不難寫出這樣的代碼

    public TreeNode invertTree(TreeNode root) {if (root==null){return null;}invertTree(root.right); invertTree(root.left);// 第一輪這里的root一定是倒數第二層的root,就可以交換其左孩子和右孩子了TreeNode temp = root.right;root.right = root.left;root.left = temp;return root;}``` # 并查集https://leetcode-cn.com/problems/number-of-operations-to-make-network-connected/ ```java package org.ling.offer.project01;public class MakeCon {static int fa[];static int count;public static int makeConnected(int n, int[][] connections) {init(n);count = n-1;// fa =new int[n];//如果初始布線數小于 n - 1,那么一定不能使所有計算機連通if (connections.length < n - 1) {return -1;}for (int i = 0; i < connections.length ; i++) {if (uoin(connections[i][0],connections[i][1])){count--;}}return count;}public static boolean uoin(int x,int y ){int xn = find(x);int yn = find(y);if (xn!=yn){fa[xn] = yn;return true;}return false;}public static void init(int n) {fa = new int[n];for (int i = 0; i <n ; i++) {fa[i] = i;}}// 這個梗方便理解,但實際上還是會超時static int find(int x){int r = x;while (fa[r] != r){r = fa[r]; // 找到根}// 這是x的值為x的根,讓那個我們把將 x → p 路徑上的所有節點的 fa 更新為 pwhile(fa[x]!=x){int t = fa[x];fa[x] = r;x =t ;}return x;}// 使用方法2利用遞歸棧來解決會好很多,因為前者會跑兩次static int find2(int x){if(fa[x]==x){return x;}return fa[x]=find(fa[x]);}public static void main(String[] args) {int[][] c ={{0,1},{0,2},{1,2}};makeConnected(4,c);} }

    這道題也可以用dfs做,不妨復習下dfs,但是和島嶼那道題不太一樣的是,這道題的邊才是我們要找的鄰接關系。

    public class makeCon {static List<Integer>[] edges;static boolean[] used;public static int makeConnected(int n, int[][] connections) {if (connections.length < n - 1) {return -1;}edges = new List[n];for (int i = 0; i < n; i++) {edges[i] = new ArrayList<Integer>();}// 先要構建鄰接矩陣for (int[] con : connections) {edges[con[0]].add(con[1]);edges[con[1]].add(con[0]);}used = new boolean[n];int ans = 0;for (int i = 0; i < n; i++) {if (!used[i]) {dfs(i);++ans;}}return ans-1;}private static void dfs(int u) {used[u] =true;for (int v:edges[u]) {if (!used[v]){dfs(v);}}}public static void main(String[] args) {int[][] c ={{0,1},{0,2},{1,2}};makeConnected(4,c);}

    鳴謝:
    https://blog.csdn.net/lxzxmm/article/details/81305039

    總結

    以上是生活随笔為你收集整理的力扣刷题系列总结的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。