当前位置: 首页 > news >正文

代码随想录刷题复习day01

day01

数组-二分查找

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

数组要用[]

数组长度nums.length

中文分号

1.当nums[mid] != target时,应该更改区间范围,当前这个mid不应该考虑了,因为当前这个mid已经没有等于
target的可能性了**(左闭右闭 的时候 right=mid不正确)**

2.考虑到算法逻辑上的合理性,在查找之前应该判断我们的目标元素在不在数组之中,不在的话直接返回-1,在的话才开始二分查找

// 避免当 target 小于nums[0] nums[nums.length - 1]时多次循环运算
if (target < nums[0] || target > nums[nums.length - 1]) {
return -1;
}

3.(max- min) >> 1相当于(min + max) / 2

  if (nums[mid] > target)right = mid - 1;

链表-移除链表元素

/*** Definition for singly-linked list.* public class ListNode {*     int val;*     ListNode next;*     ListNode() {}*     ListNode(int val) { this.val = val; }*     ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public ListNode removeElements(ListNode head, int val) {ListNode dummyhead=new ListNode(-1);dummyhead.next=head;ListNode cur=dummyhead;while(cur.next!=null) {if(cur.next.val!=val) cur=cur.next;else cur.next=cur.next.next;}return dummyhead.next;}
}

1.移除是 cur.next=cur.next.next;而不是cur=cur.next.next;

2.定义虚拟头节点dummyhead统一操作

3.终止条件cur.next!=null,一直往下走是一个持续的过程 用while

哈希表-有效字母异位词

定义一个存放26个小写字母的整数数组 int hash[26]

i=1,若索引1为字符’b’,则’b’-‘a’=1,则hash[1]++;hash索引1存放的是b出现的次数,最后比较数组 hash[26]时候
全0即可

class Solution {public boolean isAnagram(String s, String t) {int[] hash = new int[26];// 定义一个存放26个小写字母的整数数组hashif (s.length() == t.length()) {for (int i = 0; i < s.length(); i++) {hash[s.charAt(i) - 'a']++;hash[t.charAt(i) - 'a']--;}}else return false;for (int i = 0; i < 26; i++) {if (hash[i] != 0) {return false;}}return true;}
}

String类相关的API
s.charAt(i) 返回指定索引处char值
s.length() 返回字符串长度

字符串-反转字符串

取巧 调用库函数

将字符数字遍历使用StringBuilder拼接 然后reverse 再调用 s[i] = sb.charAt(i);

class Solution {public void reverseString(char[] s) {StringBuilder sb = new StringBuilder();for (char c : s) sb.append(c);sb.reverse();for (int i = 0; i < s.length; i++) {s[i] = sb.charAt(i);}}
}

如果一道题目使用库函数可以很简单的做出来,建议不要使用 不然题目没啥意义

双指针

class Solution {public void reverseString(char[] s) {int left=0;int right=s.length-1;while(right>=left){char temp=s[left];s[left] = s[right];s[right] = temp;left++;right--;}}
}

栈与队列-用栈实现队列

class MyQueue {Stack<Integer> stackIn;Stack<Integer> stackOut;// 初始化两个栈public MyQueue() {stackIn = new Stack<>(); // 负责进栈stackOut = new Stack<>(); // 负责出栈}public void push(int x) {stackIn.push(x);// 压入入栈stackIn的栈顶}// pop 和push之前需要先判断栈是否为空public int pop() {// 一旦有输出操作,就需要把入栈内的元素全部压入出栈,然后在做相关操作while (!stackIn.empty()) {stackOut.push(stackIn.pop());}return stackOut.pop();}public int peek() {// 一旦有输出操作,就需要把入栈内的元素全部压入出栈,然后在做相关操作while (!stackIn.empty()) {stackOut.push(stackIn.pop());}return stackOut.peek();}public boolean empty() {// 两个栈为空才为空return (stackIn.empty() && stackOut.empty());}
}/*** Your MyQueue object will be instantiated and called as such:* MyQueue obj = new MyQueue();* obj.push(x);* int param_2 = obj.pop();* int param_3 = obj.peek();* boolean param_4 = obj.empty();*/

一开始将stackInstackOut 在构造函数中被定义为局部变量,这样它们在构造函数结束时就会被销毁。应该将它们定义为类的成员变量。

在这里插入图片描述

二叉树1-二叉树的递归遍历

以下以前序遍历为例:

  1. 确定递归函数的参数和返回值:因为要打印出前序遍历节点的数值,所以参数里需要传入vector来放节点的数值,除了这一点就不需要再处理什么数据了也不需要有返回值,所以递归函数返回类型就是void,代码如下:
public void front(TreeNode root,List<Integer> res){//确定递归函数的参数及返回值

2.确定终止条件:在递归的过程中,如何算是递归结束了呢,当然是当前遍历的节点是空了,那么本层递归就要结束了,所以如果当前遍历的这个节点是空,就直接return,代码如下:

if(root==null) return;

3.确定单层递归的逻辑:前序遍历是中左右的顺序,所以在单层递归的逻辑,是要先取中节点的数值,代码如下:

//确定单层递归逻辑res.add(root.val);//中front(root.left,res);//左front(root.left,res);//右

单层递归的逻辑就是按照中左右的顺序来处理的,这样二叉树的前序遍历,基本就写完了,再看一下完整代码:

/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val = val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this.val = val;* this.left = left;* this.right = right;* }* }*/
// 前序遍历顺序:中-左-右 递归
class Solution {public List<Integer> preorderTraversal(TreeNode root) {List<Integer> res = new ArrayList<Integer>();front(root, res);return res;}public void front(TreeNode root,List<Integer> res){//确定递归函数的参数及返回值//确定终止条件if(root==null) return;//确定单层递归逻辑res.add(root.val);//中front(root.left,res);//左front(root.left,res);//右}
}

递归要严格按照三部曲来

res定义为全局变量,共同维护

搞清楚前序遍历为 中-左-右

后序遍历:

public void back(TreeNode root, List<Integer> res) {// 1.确定递归函数的参数和返回值
if (root == null) return;// 2.确定终止条件
// 3.确定单层递归的逻辑
back(root.left, res);// 左
back(root.right, res);// 右
res.add(root.val);// 中
}

中序遍历:

public void mid(TreeNode root,List<Integer> res){// 1.确定递归函数的参数和返回值
if(root==null) return;// 2.确定终止条件
// 3.确定单层递归的逻辑
mid(root.left,res);//左
res.add(root.val);//中
mid(root.right,res);//右
}

回溯-组合

回溯基本介绍
递归+for循环
回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案
回溯可以解决哪些问题?
组合、切割、子集、排列、棋盘问题
如何理解回溯法?
抽象成一种图形结构,所有的回溯法都可以抽象成一个树形结构(N叉树 )
回溯算法通过递归来控制有多少层for循环,递归里的每一层就是一个for循环

严格按照回溯三部曲来

回溯法的搜索过程就是一个树型结构的遍历过程,在如下图中,可以看出for循环用来横向遍历,递归的过程是纵向遍历

77.组合1

class Solution {public List<List<Integer>> combine(int n, int k) {LinkedList<Integer> path = new LinkedList<>();// 用于存放符合条件的单一结果List<List<Integer>> res = new ArrayList<>();// 用于存放最后的结果集backtracking(n,k,1);return res;}void backtracking(int n, int k, int startIndex) { // 1.确定递归参数及返回值// path这个数组的大小如果达到k,说明我们找到了一个子集大小为k的组合了,在图中path存的就是根节点到叶子节点的路径。if (path.size() == k) { // 2.回溯函数终止条件res.add(new ArrayList<>(path));return;}// 3.单层递归逻辑for (int i = startIndex; i <= n; i++) {path.add(i);backtracking(n, k, i + 1);//递归下一层path.removeLast();//回溯的操作了,撤销本次处理的结果}}
}

1.pathres 应该是类的成员变量,这样在递归调用过程中可以正确地共享和更新它们。

2.res 的类型应该是 List<List<Integer>>,这样可以匹配 combine 方法的返回类型。

3.大多数剪枝操作都在for循环中的i的范围做文章的

for(int i=startIndex; i<=n-(k-path.size())+1;i++)

77.组合4

4.在回溯过程中不能直接将 path 添加到 res,这样没有创建 path 的副本。这会导致 res 中的所有列表元素最终都引用同一个 path 对象。因此,最后 res 中的所有元素都会是相同的,且等于最后一个 path 的状态

5.注意startIndex,代表下一层递归的起始搜索位置

二叉树2-二叉树的迭代遍历

我们先看一下前序遍历。

前序遍历是中左右,每次先处理的是中间节点,那么先将根节点放入栈中,然后将右孩子加入栈,再加入左孩子。

为什么要先加入 右孩子,再加入左孩子呢? 因为这样出栈的时候才是中左右的顺序。

动画如下:

二叉树前序遍历(迭代法)

// 前序遍历顺序:中-左-右,入栈顺序:中-右-左
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 node = stack.pop();//中节点出栈才会将子节点压入栈res.add(node.val);// 中if (node.right != null) {stack.push(node.right);// 右节点压入栈}if (node.left != null) {stack.push(node.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 node = stack.pop();//中节点出栈才会将子节点压入栈res.add(node.val);// 中if (node.left != null) {// 左节点压入栈stack.push(node.left);}if (node.right != null) {// 右节点压入栈stack.push(node.right);}}Collections.reverse(res);//调用 Collections工具类反转数组return res;}
}

在这里插入图片描述

在这里插入图片描述

中序

中序:左右中

分析一下为什么刚刚写的前序遍历的代码,不能和中序遍历通用呢,因为前序遍历的顺序是中左右,先访问的元素是中间节点,要处理的元素也是中间节点,所以刚刚才能写出相对简洁的代码,因为要访问的元素和要处理的元素顺序是一致的,都是中间节点。

那么再看看中序遍历,中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了处理顺序和访问顺序是不一致的。

那么在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。

动画如下:

二叉树中序遍历(迭代法)

先中节点左节点全部加进去,然后逐个弹出做处理

指针 cur 从根节点开始,沿着左子树不断深入,当无法继续深入时,从栈中弹出节点,记录节点值,然后转向右子树。

class Solution {public List<Integer> inorderTraversal(TreeNode root) {List<Integer> result = new ArrayList<>();Stack<TreeNode> stack = new Stack<>();TreeNode cur = root;while (cur != null || !stack.isEmpty()) {if (cur != null) {stack.push(cur); // 将访问的节点放进栈cur = cur.left;  // 左} else {cur = stack.pop(); // 从栈里弹出的数据,就是要处理的数据(放进result数组里的数据)result.add(cur.val); // 中cur = cur.right;     // 右}}return result;}
}

二叉树3-二叉树的层序遍历

层序遍历一个二叉树。就是从左到右一层一层的去遍历二叉树。这种遍历的方式和我们之前讲过的都不太一样。

需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。

而这种层序遍历方式就是图论中的广度优先遍历,只不过我们应用在二叉树上。

使用队列实现二叉树广度优先遍历,动画如下:

102二叉树的层序遍历

这样就实现了层序从左到右遍历二叉树。

代码如下:这份代码也可以作为二叉树层序遍历的模板

简洁形式

/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val = val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this.val = val;* this.left = left;* this.right = right;* }* }*/
class Solution {public List<List<Integer>> levelOrder(TreeNode root) {List<List<Integer>> res = new ArrayList<>();if (root == null) {return res; // 如果根节点为空,直接返回空的结果列表}Queue<TreeNode> que = new LinkedList<>();//双端队列que.offer(root); // 将根节点加入到队列中while(!que.isEmpty()){//非空时执行List<Integer> itemList = new ArrayList<>(); // 当前层的节点值列表int len=que.size();for(int i=1;i<=len;i++){TreeNode tempNode=que.poll();itemList.add(tempNode.val);if(tempNode.left!=null) que.offer(tempNode.left);if(tempNode.right!=null) que.offer(tempNode.right);}res.add(itemList);//将当前层的节点值列表加入到结果列表中}return res;}
}

为空的时候返回[];而不是null;

因为是每一层遍历的时候都生成了一个itemList;所以后面可以直接res.add(itemList)

Queue

在这里插入图片描述

贪心-分发饼干

贪心算法一般分为如下四步:
1.将问题分解为若干个子问题
2.找出适合的贪心策略
3.求解每一个子问题的最优解
4.将局部最优解堆叠成全局最优解
这个四步其实过于理论化了,我们平时在做贪心类的题目 很难去按照这四步去思考,真是有点“鸡肋”。
做题的时候,只要想清楚 局部最优 是什么,如果推导出全局最优,其实就够了

img

class Solution {// 思路2:优先考虑胃口,先喂饱大胃口     public int findContentChildren(int[] g, int[] s) {Arrays.sort(g);//胃口Arrays.sort(s);//饼干尺寸int count = 0;int start = s.length - 1;// 遍历胃口   for 控制 胃口,里面的 if 控制饼干。for (int index = g.length - 1; index >= 0; index--) {if (start >= 0 && g[index] <= s[start]) {start--;count++;}}return count;}
}

动态规划1-斐波那契数

动规五部曲

动规五部曲:
这里我们要用一个一维dp数组来保存递归的结果
1.确定dp数组以及下标的含义
dp[i]的定义为:第i个数的斐波那契数值是dp[i]
2.确定递推公式
为什么这是一道非常简单的入门题目呢?
因为题目已经把递推公式直接给我们了:状态转移方程 dp[i] = dp[i - 1] + dp[i - 2];
3.dp数组如何初始化
题目中把如何初始化也直接给我们了,如下:
4.确定遍历顺序
递归公式dp[i] = dp[i - 1] + dp[i - 2];中可以看出,dp[i]是依赖 dp[i - 1] 和 dp[i - 2],那么遍历的顺序一定是从前到
后遍历的
5.举例推导dp数组
按照这个递推公式dp[i] = dp[i - 1] + dp[i - 2],我们来推导一下,当N为10的时候,dp数组应该是如下的数列:
0 1 1 2 3 5 8 13 21 34 55

class Solution {public int fib(int n) {if (n <= 1)return n;int[] dp = new int[n + 1];//一定是n+1个数,因为有一个0dp[0] = 0;dp[1] = 1;for (int index = 2; index <= n; index++) {dp[index] = dp[index - 1] + dp[index - 2];}return dp[n];}
}

//一定是n+1个数,因为有一个0

动态规划2-爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 12 个台阶。你有多少种不同的方法可以爬到楼顶呢?

1.确定dp数组以及下标的含义

dp[i]: 爬到第i层楼梯,有dp[i]种方法

2.确定递推公式

如何可以推出dp[i]呢?

从dp[i]的定义可以看出,dp[i] 可以有两个方向推出来。

首先是dp[i - 1],上i-1层楼梯,有dp[i - 1]种方法,那么再一步跳一个台阶不就是dp[i]了么。

还有就是dp[i - 2],上i-2层楼梯,有dp[i - 2]种方法,那么再一步跳两个台阶不就是dp[i]了么。

那么dp[i]就是 dp[i - 1]与dp[i - 2]之和!

所以dp[i] = dp[i - 1] + dp[i - 2] 。

在推导dp[i]的时候,一定要时刻想着dp[i]的定义,否则容易跑偏。

这体现出确定dp数组以及下标的含义的重要性!

3.dp数组如何初始化

再回顾一下dp[i]的定义:爬到第i层楼梯,有dp[i]种方法。

那么i为0,dp[i]应该是多少呢,这个可以有很多解释,但基本都是直接奔着答案去解释的。

例如强行安慰自己爬到第0层,也有一种方法,什么都不做也就是一种方法即:dp[0] = 1,相当于直接站在楼顶。

但总有点牵强的成分。

那还这么理解呢:我就认为跑到第0层,方法就是0啊,一步只能走一个台阶或者两个台阶,然而楼层是0,直接站楼顶上了,就是不用方法,dp[0]就应该是0.

其实这么争论下去没有意义,大部分解释说dp[0]应该为1的理由其实是因为dp[0]=1的话在递推的过程中i从2开始遍历本题就能过,然后就往结果上靠去解释dp[0] = 1

从dp数组定义的角度上来说,dp[0] = 0 也能说得通。

需要注意的是:题目中说了n是一个正整数,题目根本就没说n有为0的情况。

所以本题其实就不应该讨论dp[0]的初始化!

我相信dp[1] = 1,dp[2] = 2,这个初始化大家应该都没有争议的。

所以我的原则是:不考虑dp[0]如何初始化,只初始化dp[1] = 1,dp[2] = 2,然后从i = 3开始递推,这样才符合dp[i]的定义。

class Solution {// 常规方式public int climbStairs(int n) {int[] dp = new int[n + 1];dp[0] = 1;dp[1] = 1;for (int i = 2; i <= n; i++) {dp[i] = dp[i - 1] + dp[i - 2];}return dp[n];}
}

单调栈-每日温度

请根据每日 气温 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。

例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。

//单调栈:适合求解当前元素左边或者右边第一个比当前元素大/小的元素。强调找到

递增:找第一个比他大的

递减:找第一个比他小的

栈内存放下标,记录我们遍历过的元素,然后和当前遍历的元素作对比T[i] ?T[st.pop()]

思路

在这里插入图片描述

在这里插入图片描述

  • 情况一:当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况
  • 情况二:当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况
  • 情况三:当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况
class Solution {// 版本 1public int[] dailyTemperatures(int[] temperatures) {int lens = temperatures.length;int[] res = new int[lens];/*** 如果当前遍历的元素 大于栈顶元素,表示 栈顶元素的 右边的最大的元素就是 当前遍历的元素,* 所以弹出 栈顶元素,并记录* 如果栈不空的话,还要考虑新的栈顶与当前元素的大小关系* 否则的话,可以直接入栈。* 注意,单调栈里 加入的元素是 下标。*/Deque<Integer> stack = new LinkedList<>();stack.push(0);for (int i = 1; i < lens; i++) {if (temperatures[i] <= temperatures[stack.peek()]) { // 当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况,直接加入stack.push(i);} else {while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) {res[stack.peek()] = i - stack.peek();// 当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况,表示栈顶元素的右边的最大的元素就是当前遍历的元素,所以弹出栈顶元素,并记录stack.pop();}stack.push(i);}}return res;}
}

多态:

Deque<Integer> stack=new LinkedList<>();

Deque<Integer> stack = new LinkedList<>(); 确实是一个多态的例子。在Java中,多态性(Polymorphism)是指对象可以表现为多种形态,这通常通过继承和接口实现。在这个例子中,Deque 是一个接口,而 LinkedList 是一个实现了 Deque 接口的具体类。

具体分析如下:

  1. 接口和实现Deque 是一个接口,定义了双端队列的行为。LinkedList 是一个类,实现了 Deque 接口(以及其他接口如 ListQueue),并提供了具体的实现。
  2. 多态性:通过使用 Deque<Integer> stack,你可以将任何实现了 Deque 接口的类的实例赋值给 stack。在这个例子中,stack 被赋值为 LinkedList<Integer> 的实例,但它也可以是其他实现了 Deque 接口的类的实例,如 ArrayDeque<Integer>。这种设计允许代码更加灵活和可扩展,因为你可以在不改变代码其他部分的情况下,改变 stack 的具体实现。
  3. 面向接口编程:这是一种良好的编程实践,称为面向接口编程。通过依赖接口而不是具体实现,代码变得更具灵活性和可维护性。

如何判断是递增还是递减的单调栈呢?

递增:找第一个比他大的

递减:找第一个比他小的

栈内存放下标,记录我们遍历过的元素,然后和当前遍历的元素作对比T[i] ?T[st.pop()]

图论-所有可能路径

给你一个有 n 个节点的 有向无环图(DAG),请你找出所有从节点 0 到节点 n-1 的路径并输出(不要求按特定顺序

graph[i] 是一个从节点 i 可以访问的所有节点的列表(即从节点 i 到节点 graph[i][j]存在一条有向边)。

示例 1:

img

输入:graph = [[1,2],[3],[3],[]]
输出:[[0,1,3],[0,2,3]]
解释:有两条路径 0 -> 1 -> 3 和 0 -> 2 -> 3
private List<List<Integer>> result = new ArrayList<>(); // 收集符合条件的路径
private List<Integer> path = new ArrayList<>(); // 0节点到终点的路径

DFS深度优先搜索

1.确认递归函数,参数

首先我们dfs函数一定要存一个图,用来遍历的,还要存一个目前我们遍历的节点,定义为x,下标

至于 单一路径,和路径集合可以放在全局变量,那么代码是这样的:

private List<List<Integer>> result = new ArrayList<>(); // 收集符合条件的路径
private List<Integer> path = new ArrayList<>(); // 0节点到终点的单一路径// x:目前遍历的节点// graph:存当前的图private void dfs(int[][] graph, int x)

2.确认终止条件

什么时候我们就找到一条路径了?

当目前遍历的节点 为 最后一个节点的时候,就找到了一条,从 出发点到终止点的路径。

当前遍历的节点,我们定义为x,最后一点节点,就是 graph.length - 1(因为题目描述是找出所有从节点 0 到节点 n-1 的路径并输出)。

所以 但 x 等于 graph.size() - 1 的时候就找到一条有效路径。 代码如下:

  // 要求从节点 0 到节点 n-1 的路径并输出,所以是 graph.length - 1if (x == graph.length - 1) { // 找到符合条件的一条路径result.add(new ArrayList<>(path));// 收集有效路径return;}

3.处理目前搜索节点出发的路径

接下来是走 当前遍历节点x的下一个节点。

首先是要找到 x节点链接了哪些节点呢? 遍历方式是这样的:

 for (int i = 0; i < graph[x].length; i++) { // 遍历节点n链接的所有节点

接下来就是将 选中的x所连接的节点,加入到 单一路径来。

path.add(graph[x][i]); // 遍历到的节点加入到路径中来

一些录友可以疑惑这里如果找到x 链接的节点的,例如如果x目前是节点0,那么目前的过程就是这样的:

img

二维数组中,graph[x] [i] 都是x链接的节点,当前遍历的节点就是 graph[x][i]

进入下一层递归

dfs(graph, graph[x][i]); // 进入下一层递归

最后就是回溯的过程,撤销本次添加节点的操作。 该过程整体代码:

 for (int i = 0; i < graph[x].length; i++) { // 遍历节点n链接的所有节点path.add(graph[x][i]); // 遍历到的节点加入到路径中来dfs(graph, graph[x][i]); // 进入下一层递归path.remove(path.size() - 1); // 回溯,撤销本节点//或者这么写: path.removeLast(); // 回溯,撤销本节点}

DFS

//dfs
class Solution {private List<List<Integer>> result = new ArrayList<>(); // 收集符合条件的路径private List<Integer> path = new ArrayList<>(); // 0节点到终点的路径public List<List<Integer>> allPathsSourceTarget(int[][] graph) {path.add(0); // 无论什么路径已经是从0节点出发dfs(graph, 0); // 开始遍历return result;}// x:目前遍历的节点// graph:存当前的图private void dfs(int[][] graph, int x) {// 要求从节点 0 到节点 n-1 的路径并输出,所以是 graph.length - 1if (x == graph.length - 1) { // 找到符合条件的一条路径//   从0---->3result.add(new ArrayList<>(path));// 收集有效路径return;}for (int i = 0; i < graph[x].length; i++) { // 遍历节点n链接的所有节点path.add(graph[x][i]); // 遍历到的节点加入到路径中来 0->1  //21行回来 变成0->1->3 r 到了15行 result加入0->1->3路径 return回21,撤销dfs(graph, graph[x][i]); // 进入下一层递归 进入节点1path.removeLast(); // 回溯,撤销本节点}}
}

打断点debug看具体的运行流程是怎么样的

相关文章:

  • Java多线程设计模式之保护性暂挂模式
  • 关于Threejs的使用二
  • 东芝-Soft Limit 报警及其解决办法
  • 代码随想录算法训练营第29天(贪心)|455.分发饼干、376. 摆动序列、53. 最大子序和
  • C语言 图的基础知识
  • HTTP/2 协议学习
  • VMware ESXi 8.0U2c macOS Unlocker OEM BIOS ConnectX-3 网卡定制版 (集成驱动版)
  • 流程图工具评测:十大热门软件对比
  • Spring Boot 中如何解决跨域问题、Spring Cloud 5大组件、微服务的优缺点是什么?
  • 模拟原神圣遗物系统-小森设计项目,设计圣遗物(生之花,死之羽,时之沙,空之杯,理之冠)抽象类
  • 【软件测试】软件测试入门
  • AOSP开发环境搭建
  • 志愿服务管理系统的设计
  • 【全网最全最详细】RabbitMQ面试题
  • 【Go】用 DBeaver、db browser 和 SqlCipher 读取 SqlCipher 数据库
  • 【跃迁之路】【585天】程序员高效学习方法论探索系列(实验阶段342-2018.09.13)...
  • 2017-09-12 前端日报
  • CSS实用技巧
  • javascript面向对象之创建对象
  • Node项目之评分系统(二)- 数据库设计
  • Sass 快速入门教程
  • spring cloud gateway 源码解析(4)跨域问题处理
  • swift基础之_对象 实例方法 对象方法。
  • Vue--数据传输
  • 分享自己折腾多时的一套 vue 组件 --we-vue
  • 数据科学 第 3 章 11 字符串处理
  • 双管齐下,VMware的容器新战略
  • 提升用户体验的利器——使用Vue-Occupy实现占位效果
  • 小程序 setData 学问多
  • linux 淘宝开源监控工具tsar
  • RDS-Mysql 物理备份恢复到本地数据库上
  • 测评:对于写作的人来说,Markdown是你最好的朋友 ...
  • ​3ds Max插件CG MAGIC图形板块为您提升线条效率!
  • #1015 : KMP算法
  • #我与Java虚拟机的故事#连载06:收获颇多的经典之作
  • ()、[]、{}、(())、[[]]等各种括号的使用
  • (el-Date-Picker)操作(不使用 ts):Element-plus 中 DatePicker 组件的使用及输出想要日期格式需求的解决过程
  • (solr系列:一)使用tomcat部署solr服务
  • (初研) Sentence-embedding fine-tune notebook
  • (附源码)spring boot公选课在线选课系统 毕业设计 142011
  • (附源码)ssm考生评分系统 毕业设计 071114
  • (实战)静默dbca安装创建数据库 --参数说明+举例
  • (一)eclipse Dynamic web project 工程目录以及文件路径问题
  • .NET CF命令行调试器MDbg入门(四) Attaching to Processes
  • .NET Core 和 .NET Framework 中的 MEF2
  • .net mvc actionresult 返回字符串_.NET架构师知识普及
  • .NET 解决重复提交问题
  • .NET 项目中发送电子邮件异步处理和错误机制的解决方案
  • .Net 中的反射(动态创建类型实例) - Part.4(转自http://www.tracefact.net/CLR-and-Framework/Reflection-Part4.aspx)...
  • .NET/C# 判断某个类是否是泛型类型或泛型接口的子类型
  • .NET6实现破解Modbus poll点表配置文件
  • .NET8使用VS2022打包Docker镜像
  • .net程序集学习心得
  • .py文件应该怎样打开?
  • @Import注解详解