代码随想录刷题复习day01

news2024/12/22 13:56:40

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看具体的运行流程是怎么样的

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

机器学习案例|使用机器学习轻松预测信用卡坏账风险,极大程度降低损失

01、案例说明 对于模型的参数&#xff0c;除了使用系统的设定值之外&#xff0c;可以进行再进一步的优化而得到更好的结果。RM提供了几种参数优化的方法&#xff0c;能够让整体模型的效率提高。而其使用的概念&#xff0c;仍然是使用计算机强大的计算能力&#xff0c;对于不同…

动态轮换代理在多账户管理中有何用处?

如果您要处理多个在线帐户&#xff0c;选择正确的代理类型对于实现流畅的性能至关重要。但最适合这项工作的代理类型是什么&#xff1f; 为了更好地管理不同平台上的多个账户并优化成本&#xff0c;动态住宅代理IP通常作用在此。 一、什么是轮换代理&#xff1f; 轮换代理充当…

SpringSecurity实战入门——认证

项目代码 gson/spring-security-demo 简介 Spring Security 是 Spring 家族中的一个安全管理框架。相比与另外一个安全框架Shiro,它提供了更丰富的功能,社区资源也比Shiro丰富。 一般来说中大型的项目都是使用SpringSecurity来做安全框架。小项目有Shiro的比较多,因为相比…

探索交互设计:五大关键维度全面剖析

交互式设计是用户体验&#xff08;UX&#xff09;设计的重要组成部分。在本文中&#xff0c;我将向大家解释什么是交互设计并简要描述交互设计师通常每天都做什么。 一、什么是交互设计 交互式设计用简单的术语来理解就是用户和产品之间的交互。在大多数情况下&#xff0c;当…

嵌入式Linux 中常见外设屏接口分析

今天将梳理下嵌入式外设屏幕接口相关的介绍,对于一个嵌入式驱动开发工程师,对屏幕都可能接触到一些相关的的调试,这里首先把基础相关的知识梳理。 1. 引言 在嵌入式开发过程中,使用到的液晶屏有非常多的种类,根据不同技术和特性分类,会接触到TN液晶屏,TN液晶屏 VA液晶屏…

JDBC(简介、入门与IDEA中导入MySQL的驱动)

&#xff08;建议学完 MySQL 的基础部分&#xff09; JDBC——简而言之&#xff1a;用 Java 语言操作数据库。 Java DataBase Connectivity&#xff08;Java 语言连接数据库&#xff09; 目录 一、引言 &#xff08;1&#xff09;基本介绍 &#xff08;2&#xff09;JDBC 简…

【代码随想录】【算法训练营】【第44天】 [322]零钱兑换 [279]完全平方数 [139]单词拆分

前言 思路及算法思维&#xff0c;指路 代码随想录。 题目来自 LeetCode。 day 44&#xff0c;周四&#xff0c;坚持不住了~ 题目详情 [322] 零钱兑换 题目描述 322 零钱兑换 解题思路 前提&#xff1a; 思路&#xff1a; 重点&#xff1a; 代码实现 C语言 [279] 完全…

代码随想录算法训练营第29天(贪心)|455.分发饼干、376. 摆动序列、53. 最大子序和

455.分发饼干 题目链接&#xff1a;455.分发饼干 文档讲解&#xff1a;代码随想录 状态&#xff1a;so easy 思路&#xff1a;对胃口和饼干大小排序&#xff0c;小胃口对应小饼干&#xff0c;不满足的话用下一块饼干试探。 题解&#xff1a; public int findContentChildren(i…

自动化测试Robot FrameWork框架

一、简介 Robot FrameWork是完全基于Python实现的开源的自动化测试框架&#xff0c;RF已经封装好的各个模块&#xff0c;基于关键字驱动的形式来实现的自动化测试。其case采用表格形式易读&#xff0c;且支持BDD&#xff0c;可容纳各种外置库&#xff0c;可以继承Selenium、Ap…

【机器学习】基于稀疏识别方法的洛伦兹混沌系统预测

1. 引言 1.1. DNN模型的来由 从数据中识别非线性动态学意味着什么&#xff1f; 假设我们有时间序列数据&#xff0c;这些数据来自一个&#xff08;非线性&#xff09;动态学系统。 识别一个系统意味着基于数据推断该系统的控制方程。换句话说&#xff0c;就是找到动态系统方…

【etcd】etcd单机安装及简单操作

https://blog.csdn.net/Mr_XiMu/article/details/125026635 https://blog.csdn.net/m0_73192864/article/details/136509244 etcd在生产环境中一般为集群方式部署 etcd使用的2个默认端口号&#xff1a;2379和2380 2379&#xff1a;用于客户端通信(类似于sqlserver的1433&#x…

视频融合共享平台LntonCVS视频监控安防系统运用多视频协议建设智慧园区方案

智慧园区&#xff0c;作为现代化城市发展的重要组成部分&#xff0c;不仅推动了产业的升级转型&#xff0c;也成为了智慧城市建设的核心力量。随着产业园区之间的竞争日益激烈&#xff0c;如何打造一个功能完善、智能化程度高的智慧园区&#xff0c;已经成为了业界广泛关注的焦…

五十、openlayers官网示例JSTS Integration解析——使用JSTS 库来处理几何缓冲区并在地图上显示结果

官网demo地址&#xff1a; JSTS Integration 这篇讲了如何在地图上添加缓冲图形 什么叫做缓冲几何&#xff1f; 几何缓冲&#xff08;Geometric Buffering&#xff09;是指在 GIS&#xff08;地理信息系统&#xff09;和计算几何中&#xff0c;围绕一个几何对象创建一个具有…

时空预测 | 基于深度学习的碳排放时空预测模型

时空预测 模型描述 数据收集和准备&#xff1a;收集与碳排放相关的数据&#xff0c;包括历史碳排放数据、气象数据、人口密度数据等。确保数据的质量和完整性&#xff0c;并进行必要的数据清洗和预处理。 特征工程&#xff1a;根据问题的需求和领域知识&#xff0c;对数据进行…

Walrus:去中心化存储和DA协议,可以基于Sui构建L2和大型存储

Walrus是为区块链应用和自主代理提供的创新去中心化存储网络。Walrus存储系统今天以开发者预览版的形式发布&#xff0c;面向Sui开发者征求反馈意见&#xff0c;并预计很快会向其他Web3社区广泛推广。 通过采用纠删编码创新技术&#xff0c;Walrus能够快速且稳健地将非结构化数…

5款堪称变态的AI神器,焊死在电脑上永不删除!

一 、AI视频合成工具——Runway&#xff1a; 第一款RunWay&#xff0c;你只需要轻轻一抹&#xff0c;视频中的元素就会被擦除&#xff0c;再来轻轻一抹&#xff0c;直接擦除&#xff0c;不喜欢这个人直接擦除&#xff0c;一点痕迹都看不出来。 除了视频擦除功能外&#xff0c;…

第一个Neety程序

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;Netty ⛺️稳中求进&#xff0c;晒太阳 加入依赖 <dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.39.F…

【Spine学习10】之 创建新骨骼时,自动绑定图片和插槽的快捷方式

两天没更新了。 遇到一些难解的难题 用的版本是破解版 不知道为啥现在的教程非常地快 明明有些细节很重要还略过讲 所以创建骨骼这里 基本创建是都会 可是骨骼一多 实际工作中的重命名也太麻烦了 。 这就需要学习快捷创建方式&#xff1a; <将对应图片自动绑定到新骨骼上并…

哔哩哔哩视频URL解析原理

哔哩哔哩视频URL解析原理 视频网址解析视频的原理通常涉及以下几个步骤&#xff1a; 1、获取视频页面源代码&#xff1a;通过HTTP请求获取视频所在网页的HTML源代码。这一步通常需要处理反爬虫机制&#xff0c;如验证码或用户登录。 2、解析页面源代码&#xff1a;分析HTML源代…

【上海交大】博士生年度进展报告模板

上海交通大学 博士生年度进展报告模板 比较不好找&#xff0c;在交我办中发起申请流程后才能看到链接&#xff0c;链接如下&#xff1a; https://www.gs.sjtu.edu.cn/xzzx/pygl/15