代码随想录刷题复习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;
            else
                return 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个小写字母的整数数组hash
        if (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个数,因为有一个0
        dp[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 {
    // 版本 1
    public 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 - 1
        if (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 - 1
        if (x == graph.length - 1) { // 找到符合条件的一条路径//   从0---->3
            result.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]); // 进入下一层递归 进入节点1
            path.removeLast(); // 回溯,撤销本节点
        }
    }
}

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/728818.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HoVer-Net复现:手把手带你实现细胞核的分割与分类,并输出叠加图像|24-06-21

小罗碎碎念 先说一下&#xff0c;只要你跟着我一步一步走&#xff0c;你能实现的效果——对细胞核进行分割和分类&#xff0c;并在原始图像上以颜色叠加的方式直观地展示这些结果。 昨天我在交流群里进行了一下预热&#xff0c;并且提供了一些前期的教程&#xff0c;反响还不…

会声会影2024专业免费版下载附带激活码序列号

&#x1f31f; 会声会影2024&#xff1a;你的视频编辑新伙伴&#xff01;大家好&#xff0c;今天来给你们安利一个超级棒的视频编辑软件——会声会影2024最新版本&#xff01;作为一位热爱创作的小伙伴&#xff0c;找到一款既强大又易用的视频编辑工具真的太重要了。而会声会影…

CentOS 8.5 - 配置ssh的免密登录

文章目录 生成ssh密钥公钥内容放入服务器 生成ssh密钥 在本地主机安装 ssh工具&#xff0c;并生成公钥、私钥。 # 命令行输入 ssh-keygen -r rsa# 会在当前用户的家目录下生成一个.ssh目录公钥内容放入服务器 将上一步生成的id_rsa.pub公钥的内容复制到远程服务器 # 编辑文…

【Golang - 90天从新手到大师】Day11 - 包的管理

系列文章合集 Golang - 90天从新手到大师 Go语言中包的使用 Go语言使用包&#xff08;package&#xff09;这种语法元素来组织源码&#xff0c;所有语法可见性均定义在package这个级别&#xff0c;与Java 、python等语言相比&#xff0c;这算不上什么创新&#xff0c;但与C传…

跨区域文件管控解决方案,一文了解

跨区域文件管控是一个涉及在不同地域或区域之间管理和控制文件的过程&#xff0c;它包括安全性、合规性和管理效率等多个方面。以下是一些关键的考量因素&#xff1a; 1.安全性&#xff1a;确保在传输过程中文件不被截获、篡改或泄露。使用加密技术保护文件&#xff0c;并确保传…

递推和递归

递推 何为递推 用若干步可重复运算来描述复杂问题的方法 递推算法是一种用若干步可重复运算来描述复杂问题的方法。递推是序列计算中的一种常用算法。通常是通过计算前面的一些项来得出序列中的指定项的值。 经典例题 import java.util.Scanner;public class Tuzi {static …

java面向对象(上)

一.面向对象与面向过程 1.面向过程 面向过程(procedure Oriented Programming),简称POP,主要思想就是将问题分解成一个个步骤去解决,把这个步骤称为函数. 典型语言:C语言 优点:可以大大简化代码 缺点:当代码量过大时,不方便维护 2.面向对象 面向对象(Object Oriented Pr…

酷开科技丨引领家庭娱乐新潮流,酷开系统带你开启多彩生活新篇章

在繁忙的都市生活节奏中&#xff0c;人们对生活品质的追求从未停歇。家庭娱乐作为提升生活质量的重要部分&#xff0c;随着科技进步和个性化需求的增长&#xff0c;已经发生了翻天覆地的变化。多样化的娱乐方式不仅为家庭生活增添了色彩&#xff0c;也为家庭成员间的相聚带来了…

一站式家装服务管理系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;装修风格管理&#xff0c;主材管理&#xff0c;用户管理&#xff0c;基础数据管理 前台账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;装修风格&#xff0…

亲测5个电脑浏览器高效技巧,保证让你搜索效率倍增!

虽然我们每个人的电脑基本每天都会用到浏览器&#xff0c;但你会发现有的人用起浏览器就是噼里啪啦的&#xff0c;找他要个什么网站他都能快速找到&#xff0c;而有的人&#xff0c;经常打开的是广告搜索的网页&#xff0c;找不到搜索的答案非常慢。小编今天就来跟你分享一下我…

系统架构设计师 - 数据库系统(1)

数据库系统 数据库系统数据库模式 ★分布式数据库 ★★★数据库设计阶段 ★★ER模型 ★关系模型 ★ ★结构约束条件完整性约束 关系代数 ★ ★ ★ ★概述自然连接 大家好呀&#xff01;我是小笙&#xff0c;本章我主要分享系统架构设计师 - 数据库系统(1)知识&#xff0c;希望内…

Kimichat使用案例023:7个提示词案例(Kimichat炒股)

文章目录 一、了解股票投资基本概念和知识二、寻求投资建议三、如何做长期投资四、找出某只股票的潜在缺点五、多个视角看股票或投资策略沃伦巴菲特视角:彼得林奇视角:埃隆马斯克视角:结论:六、比较两只股票七、总结研报或公司公告、财报等文件一、了解股票投资基本概念和知…

SpringBoot的迭代史,SpringBoot和Spring和Java和Maven和Gradle版本兼容介绍

文章目录 系统环境要求&#xff1a;Spring Boot 3.1.xSpring Boot 3.0.xSpring Boot 2.7.xSpring Boot 2.6.xSpring Boot 2.5.xSpring Boot 2.4.xSpring Boot 2.3.xSpring Boot 2.2.xSpring Boot 2.1.xSpring Boot 2.0.xSpring Boot 1.5.xSpring Boot 1.4.xSpring Boot 1.3.xSp…

深入分析 Android BroadcastReceiver (四)

文章目录 深入分析 Android BroadcastReceiver (四)1. 广播接收器的深入优化与应用1.1 实时性要求高的应用1.1.1 示例&#xff1a;音乐播放器中处理耳机插拔事件1.1.2 动态注册接收器 1.2 处理耗时操作1.2.1 示例&#xff1a;使用 IntentService 处理耗时操作 1.3 安全性管理1.…

1964springboot VUE 智慧社区可视化平台系统开发mysql数据库web结构java编程计算机网页源码maven项目

一、源码特点 springboot VUE社区可视化平台系统是一套完善的完整信息管理类型系统&#xff0c;结合springboot框架和VUE完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用springboot框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代码…

网络安全:SQL注入防范

文章目录 网络安全&#xff1a;SQL注入防范引言防范措施概览使用参数化查询示例代码 输入验证和过滤示例代码 使用ORM框架示例代码 其他防范措施结语 网络安全&#xff1a;SQL注入防范 引言 在上一篇文章中&#xff0c;我们介绍了SQL注入攻击的基础知识。本文将重点讨论如何防…

如何解决虚拟仿真教学中的设备限制和卡顿问题?|点量云流技术解决方案

在数字化时代背景下&#xff0c;高校虚拟仿真教学模型成为重要的教学辅助工具&#xff0c;通常基于UE或U3D引擎开发。尽管当前多数采用Webgl方案&#xff0c;且具备一定优势&#xff0c;但实践中仍存在不少问题。以下是常见问题分析及点量云流实时渲染技术如何提供解决方案的探…

Lynred在欧洲防务展上将展出新品——“HOT”红外传感器Seegnus。

Lynred在即将举办的巴黎欧洲防务展上将展出其令人瞩目的新品——“HOT”红外传感器Seegnus。这款专为战术视觉设计的大型阵列传感器&#xff0c;以其紧凑的封装和高分辨率的中波红外成像能力&#xff0c;无疑将为航空航天、国防和商业市场带来革命性的突破。 Seegnus传感器拥有…

Cookie、Session、Token的关系和区别

关系 Session与Cookie&#xff1a;Session通常依赖于Cookie来工作。当服务器为客户端创建一个Session时&#xff0c;它会在服务器上存储与客户端相关的信息&#xff0c;并将一个唯一的SessionID通过Cookie发送给客户端。客户端在后续的请求中会携带这个Cookie&#xff08;包含…

【面试干货】Java中的访问修饰符与访问级别

【面试干货】Java中的访问修饰符与访问级别 1、public2、protected3、默认&#xff08;没有访问修饰符&#xff09;4、private &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在Java中&#xff0c;访问修饰符用于控制类、变量、方法和构造器…