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

《代码随想录》刷题笔记——回溯篇【java实现】

文章目录

  • 组合
  • 组合总和 III
  • 电话号码的字母组合
  • 组合总和
  • 组合总和II
    • 思路
    • 代码实现
  • 分割回文串※
    • 思路
      • 字符串分割
      • 回文串判断
      • 效率优化※
  • 复原 IP 地址
    • 优化版本
  • 子集
  • 子集 II
    • 使用usedArr辅助去重
    • 不使用usedArr辅助去重
  • 递增子序列※
  • 全排列
  • 全排列 II
  • 重新安排行程
    • 题意
    • 代码
  • N 皇后
  • 解数独
    • 直接遍历
    • 优化

组合

https://leetcode.cn/problems/combinations/description/

在这里插入图片描述

public List<List<Integer>> combine(int n, int k) {List<List<Integer>> result = new ArrayList<>();// 存储搜索路径List<Integer> path = new ArrayList<>();// 从1开始递归backtrack(n, k, 1, path, result);return result;
}void backtrack(int n, int k, int startNum, List<Integer> path, List<List<Integer>> result) {if (path.size() == k) {// --if-- 组合的元素个数=k,说明找到了新的解,存储到result中List<Integer> clone = new ArrayList<>(k);for (Integer num : path) {clone.add(num);}result.add(clone);return;}for (int num = startNum; num <= n; num++) {// --for--遍历加下来的元素// 将遍历到的元素添加到搜索路径中path.add(num);// 递归,开始数量+1backtrack(n, k, num + 1, path, result);// 移除刚刚添加的数量path.remove(path.size() - 1);}
}

在这里插入图片描述

List<Integer> clone = new ArrayList<>(k);
for (Integer num : path) {clone.add(num);
}
result.add(clone);

上面的实现可以简化为

result.add(new ArrayList<>(path));

通过查看源码可以发现,构造方法传入集合的时候,JDK会自动帮我们做克隆操作

/*** Constructs a list containing the elements of the specified* collection, in the order they are returned by the collection's* iterator.** @param c the collection whose elements are to be placed into this list* @throws NullPointerException if the specified collection is null*/
public ArrayList(Collection<? extends E> c) {Object[] a = c.toArray();if ((size = a.length) != 0) {if (c.getClass() == ArrayList.class) {elementData = a;} else {elementData = Arrays.copyOf(a, size, Object[].class);}} else {// replace with empty array.elementData = EMPTY_ELEMENTDATA;}
}

通过提交运行结果可以发现,上面的实现执行用时比较长,这是为什么呢?

假设n=5,k=3,组合可选元素为1,2,3,4,5

  • 当path=[]时,组合的第一个元素为4,后面只有一个5,继续递归遍历,也只能找到两个元素的组合,无法找找到达到长度要求的组合(4、5无需遍历);
  • 当path=[1]时,组合的第二个元素为5,后面没有元素,无法找找到达到长度要求的组合(5无需遍历);

因此通过减少部分遍历来加快代码速度

public List<List<Integer>> combine(int n, int k) {List<List<Integer>> result = new ArrayList<>();// 存储搜索路径List<Integer> path = new ArrayList<>();// 从1开始递归backtrack(n, k, 1, path, result);return result;
}void backtrack(int n, int k, int startNum, List<Integer> path, List<List<Integer>> result) {if (path.size() == k) {// --if-- 组合的元素个数=k,说明找到了新的解,存储到result中result.add(new ArrayList<>(path));return;}for (int num = startNum; num <= n - (k - path.size()) + 1; num++) {// --for--遍历加下来的元素// 将遍历到的元素添加到搜索路径中path.add(num);// 递归,开始数量+1backtrack(n, k, num + 1, path, result);// 移除刚刚添加的数量path.remove(path.size() - 1);}
}

在这里插入图片描述

在创建List<Integer> path = new ArrayList<>();,因为path的长度已知,因此可以在初始化集合的时候传入集合长度,避免后面发生集合扩容,List<Integer> path = new ArrayList<>(k);

组合总和 III

https://leetcode.cn/problems/combination-sum-iii/description/

在这里插入图片描述

public List<List<Integer>> combinationSum3(int k, int n) {List<List<Integer>> result = new ArrayList<>();List<Integer> path = new ArrayList<>();backtrack(k, n, 1, result, path, 0);return result;
}private void backtrack(int k, int n, int startNum, List<List<Integer>> result, List<Integer> path, int sum) {if (path.size() == k) {// --if-- 搜索到的组合元素个数为kif (sum == n) {// --if-- sum等于n,存储组合result.add(new ArrayList<>(path));} else {// --if-- 元素个数已经为3,无需再往下递归,否则,元素个数超出k了,再搜索也是白费return;}}for (int i = startNum; i <= 9; i++) {path.add(i);backtrack(k, n, i + 1, result, path, sum + i);path.remove(path.size() - 1);}
}

在这里插入图片描述

【剪枝优化】

  • 剪枝1:path.size()>k
  • 剪枝2:sum>n
  • 剪枝3:无效组合起点,类似上一题,i <= 10- (k - path.size())
public List<List<Integer>> combinationSum3(int k, int n) {List<List<Integer>> result = new ArrayList<>();List<Integer> path = new ArrayList<>();backtrack(k, n, 1, result, path, 0);return result;
}private void backtrack(int k, int n, int startNum, List<List<Integer>> result, List<Integer> path, int sum) {if (path.size() == k) {// --if-- 搜索到的组合元素个数为kif (sum == n) {// --if-- sum等于n,存储组合result.add(new ArrayList<>(path));} else {// --if-- 元素个数已经为3,无需再往下递归,否则,元素个数超出k了,再搜索也是白费return;}} else if (sum > n) {// --if-- 搜索到的组sum已经大于n,无需再往下搜索return;}for (int i = startNum; i <= 10- (k - path.size()) ; i++) {path.add(i);backtrack(k, n, i + 1, result, path, sum + i);path.remove(path.size() - 1);}
}

在这里插入图片描述

电话号码的字母组合

https://leetcode.cn/problems/letter-combinations-of-a-phone-number/description/

在这里插入图片描述

public List<String> letterCombinations(String digits) {if (digits.length() == 0) {return new ArrayList<>();}// 先将字符串转化为char数组,方便遍历操作char[] charArray = digits.toCharArray();// 存储结果List<String> result = new ArrayList<>();// 存储搜索组合(为什么使用数组呢,因为组合的元素数量是没有变化的,而且后面需要将元素组合转化为字符串,使用char数组更加方便)char[] path = new char[digits.length()];// 从1开始递归backtrack(charArray, 0, path, result);return result;
}void backtrack(char[] charArray, int charIndex, char[] path, List<String> result) {if (charIndex == charArray.length) {// --if-- 数字已经遍历完成,存储结果result.add(new String(path));return;}char numChar = charArray[charIndex];// 根据输入的数字,转化为对应的字符数组char[] chooseCharArr = null;switch (numChar) {case '2':chooseCharArr = new char[]{'a', 'b', 'c'};break;case '3':chooseCharArr = new char[]{'d', 'e', 'f'};break;case '4':chooseCharArr = new char[]{'g', 'h', 'i'};break;case '5':chooseCharArr = new char[]{'j', 'k', 'l'};break;case '6':chooseCharArr = new char[]{'m', 'n', 'o'};break;case '7':chooseCharArr = new char[]{'p', 'q', 'r', 's'};break;case '8':chooseCharArr = new char[]{'t', 'u', 'v'};break;case '9':chooseCharArr = new char[]{'w', 'x', 'y', 'z'};break;}// 循环字符数组,挑选一个,并进入下一个数字对应的字符挑选for (int i = 0; i < chooseCharArr.length; i++) {path[charIndex] = chooseCharArr[i];backtrack(charArray, charIndex + 1, path, result);// 这里不需要回溯操作,因为下一次循环会主动将 path[charIndex] 替换成另一个元素}
}

在这里插入图片描述

【代码随想录实现】

//设置全局列表存储最后的结果
List<String> list = new ArrayList<>();public List<String> letterCombinations(String digits) {if (digits == null || digits.length() == 0) {return list;}//初始对应所有的数字,为了直接对应2-9,新增了两个无效的字符串""String[] numString = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};//迭代处理backTracking(digits, numString, 0);return list;}//每次迭代获取一个字符串,所以会涉及大量的字符串拼接,所以这里选择更为高效的 StringBuilder
StringBuilder temp = new StringBuilder();//比如digits如果为"23",num 为0,则str表示2对应的 abc
public void backTracking(String digits, String[] numString, int num) {//遍历全部一次记录一次得到的字符串if (num == digits.length()) {list.add(temp.toString());return;}//str 表示当前num对应的字符串String str = numString[digits.charAt(num) - '0'];for (int i = 0; i < str.length(); i++) {temp.append(str.charAt(i));//递归,处理下一层backTracking(digits, numString, num + 1);//剔除末尾的继续尝试temp.deleteCharAt(temp.length() - 1);}
}

组合总和

https://leetcode.cn/problems/combination-sum/description/

在这里插入图片描述

在这里插入图片描述

public List<List<Integer>> combinationSum(int[] candidates, int target) {List<List<Integer>> result = new ArrayList<>();List<Integer> path = new ArrayList<>();backtrack(candidates, target, 0, result, path);return result;
}private void backtrack(int[] candidates, int target, int startIndex,List<List<Integer>> result, List<Integer> path) {if (target == 0) {result.add(new ArrayList<>(path));return;}for (int i = startIndex; i < candidates.length; i++) {// 计算剩下的target最多还可以容纳多少个 candidates[i] int num = target / candidates[i];// 循环取不同个数 candidates[i] 的情况// 注意j从1开始,j=0的情况其实在i的循环中已经考虑到了,例如当i等于startIndex+1时,实际上就是startIndex对应的元素j=0for (int j = 1; j <= num; j++) {// 将j个元素添加到搜索路径中for (int k = 0; k < j; k++) {path.add(candidates[i]);}backtrack(candidates, target - candidates[i] * j, i + 1, result, path);// 将j个元素从搜索路径中移除for (int k = 0; k < j; k++) {path.remove(path.size() - 1);}}}
}

在这里插入图片描述

从结果可以分析出来,上面的代码还有效率问题

其中的一个原因就是如下代码,每次需要添加j个元素,然后移除j个元素

// 将j个元素添加到搜索路径中
for (int k = 0; k < j; k++) {path.add(candidates[i]);
}// 将j个元素从搜索路径中移除
for (int k = 0; k < j; k++) {path.remove(path.size() - 1);
}

改成下面的代码,效率会更好一点

public List<List<Integer>> combinationSum(int[] candidates, int target) {List<List<Integer>> result = new ArrayList<>();List<Integer> path = new ArrayList<>();backtrack(candidates, target, 0, result, path);return result;
}private void backtrack(int[] candidates, int target, int startIndex,List<List<Integer>> result, List<Integer> path) {if (target == 0) {result.add(new ArrayList<>(path));return;}for (int i = startIndex; i < candidates.length; i++) {if (target >= candidates[i]) {path.add(candidates[i]);backtrack(candidates, target - candidates[i], i, result, path);path.remove(path.size() - 1);}}
}

在这里插入图片描述

最后还存在一个剪枝策略,就是先将candidates升序排序,当遇到一个candidates[i]>target时,直接break出循环即可

 public List<List<Integer>> combinationSum(int[] candidates, int target) {List<List<Integer>> result = new ArrayList<>();List<Integer> path = new ArrayList<>();Arrays.sort(candidates);backtrack(candidates, target, 0, result, path);return result;
}private void backtrack(int[] candidates, int target, int startIndex,List<List<Integer>> result, List<Integer> path) {if (target == 0) {result.add(new ArrayList<>(path));return;}for (int i = startIndex; i < candidates.length; i++) {if (target >= candidates[i]) {path.add(candidates[i]);backtrack(candidates, target - candidates[i], i, result, path);path.remove(path.size() - 1);} else {break;}}
}

不过跑了很多次,还没有前面的效率高,可能是排序也比较耗时

在这里插入图片描述

组合总和II

https://leetcode.cn/problems/combination-sum-ii/description/

在这里插入图片描述

在这里插入图片描述

思路

如下图所示,常规的回溯方法会导致搜索到重复的组合,题目中要求不能出现重复组合。可以做一些去重操作来解决这个问题,但是性能肯定不好。我们应该在搜索的时候进行剪枝。

在这里插入图片描述

如下图所示,分支2 其实已经被 分支1 包含,因此算法在搜索过程,无需搜索分支2

在这里插入图片描述

如何识别出分支2呢,一个明显的特征是,candidates[1] = candidates[0] = 2,那当candidates[i] = candidates[i-1]时,就把i对应分支剪掉?

答案是不允许,因为这样的话,会导致分支1也没有搜索完全。如下图所示,分支a 和 分支b 都会被剪掉。如果target=7 的话,组合[2,2,3]就搜索不到了。因此需要区分 分支a 和 分支b ,分支b才是要被剪枝的。

在这里插入图片描述

那么如何区分 分支a分支b 呢?我使用一个数组useArr来帮助区分,useArr[i]表示candidates[i]在当前搜索组合中是否被使用。如下图所示,搜索过程中useArr的变化如下图所示。从图中可以很容易发现,分支a和分支b的useArr不一样,准确来说,是useArr[i-1]不一样,分支a对应的useArr[i-1]=1,而分支b对应的useArr[i-1]=0。因此当i > 0 && useArr[i - 1] == 0 && candidates[i - 1] == candidates[i]时,进行剪枝。注意,在搜索之前需要先对candidates进行升序排序,才通过判断ii-1对应的元素是否相同。

在这里插入图片描述

代码实现

public List<List<Integer>> combinationSum2(int[] candidates, int target) {List<List<Integer>> result = new ArrayList<>();List<Integer> path = new ArrayList<>();// 存储组合使用的元素,1说明元素被使用,0说明元素没有被使用int[] useArr = new int[candidates.length];// 先将数组的值升序排序Arrays.sort(candidates);backtrack(candidates, useArr, target, 0, result, path);return result;
}private void backtrack(int[] candidates, int[] useArr, int target, int startIndex,List<List<Integer>> result, List<Integer> path) {if (target == 0) {result.add(new ArrayList<>(path));return;}for (int i = startIndex; i < candidates.length; i++) {if (target < candidates[i]) {// target很小了,candidates后的数只会更大,所以可以breakbreak;}// 如果 candidates[i - 1] == candidates[i] ,说明同一层遍历到和之前的元素一样。如果useArr[i - 1] == 0,进行剪枝if (i > 0 && useArr[i - 1] == 0 && candidates[i - 1] == candidates[i]) {continue;}// 使用了candidates[i]useArr[i] = 1;path.add(candidates[i]);backtrack(candidates, useArr, target - candidates[i], i + 1, result, path);path.remove(path.size() - 1);// 去除candidates[i]的使用useArr[i] = 0;}
}

在这里插入图片描述

分割回文串※

https://leetcode.cn/problems/palindrome-partitioning/description/

在这里插入图片描述

思路

字符串分割

在这里插入图片描述

public List<List<String>> partition(String s) {List<List<String>> result = new ArrayList<>();List<String> path = new ArrayList<>();char[] charArray = s.toCharArray();backtrack(result, path, charArray, 0);return result;
}private void backtrack(List<List<String>> result, List<String> path, char[] charArray, int startIndex) {if (startIndex == charArray.length) {// --if-- 字符串切割完成了result.add(new ArrayList<>(path));return;}for (int i = startIndex; i < charArray.length; i++) {String s = "";// 截取[startIndex,i]对应子串for (int j = startIndex; j <= i; j++) {s += charArray[j];}// 将子串添加到path中path.add(s);// 递归处理剩下的子串backtrack(result, path, charArray, i + 1);// 回溯path.remove(path.size() - 1);}
}

当然上面的代码是生成所有的切割方案,并没有判断切割出来的子串是否符合回文串要求

在这里插入图片描述

回文串判断

判断一个字符串非常简单,使用双指针法。一个指针从头开始,一个指针从尾开始,判断两个指针对应的字符是否相同即可,如果出现不相同,说明字符串不是回文串。

private boolean isPalindromeSubStr(String s) {int start = 0;int end = s.length() - 1;while (start < end) {if (s.charAt(start++) != s.charAt(end--)) {return false;}}return true;
}

【总代码】

public List<List<String>> partition(String s) {List<List<String>> result = new ArrayList<>();List<String> path = new ArrayList<>();char[] charArray = s.toCharArray();backtrack(result, path, charArray, 0);return result;
}private void backtrack(List<List<String>> result, List<String> path, char[] charArray, int startIndex) {if (startIndex == charArray.length) {// --if-- 字符串切割完成了result.add(new ArrayList<>(path));return;}for (int i = startIndex; i < charArray.length; i++) {String s = "";for (int j = startIndex; j <= i; j++) {s += charArray[j];}// 判断子串是否为回文串,如果不是的,遍历下一个回文串if (isPalindromeSubStr(s) == false) {continue;}path.add(s);backtrack(result, path, charArray, i + 1);path.remove(path.size() - 1);}
}private boolean isPalindromeSubStr(String s) {int start = 0;int end = s.length() - 1;while (start < end) {if (s.charAt(start++) != s.charAt(end--)) {return false;}}return true;
}

在这里插入图片描述

通过结果,可以发现计算效率非常低,因为在搜索过程中,不断使用字符串拼接

效率优化※

直接使用substring

public List<List<String>> partition(String s) {List<List<String>> result = new ArrayList<>();List<String> path = new ArrayList<>();backtrack(result, path, s, 0);return result;
}private void backtrack(List<List<String>> result, List<String> path, String s, int startIndex) {if (startIndex == s.length()) {// --if-- 字符串切割完成了result.add(new ArrayList<>(path));return;}for (int i = startIndex; i < s.length(); i++) {// 判断子串是否为回文串,如果不是的,遍历下一个回文串String str = s.substring(startIndex, i + 1);if (isPalindromeSubStr(str) == false) {continue;}path.add(str);backtrack(result, path, s, i + 1);path.remove(path.size() - 1);}
}private boolean isPalindromeSubStr(String s) {int start = 0;int end = s.length() - 1;while (start < end) {if (s.charAt(start++) != s.charAt(end--)) {return false;}}return true;
}

效率提高了一点点

在这里插入图片描述

多跑一次,结果完全不同,所以大家不能太相信leetcode的数据统计。同一段代码在计算机中跑,运行时间有波动非常正常

在这里插入图片描述

性能还有优化的空间,还可以使用动态规划来进行优化,等学完动态规划,再回来改进

复原 IP 地址

https://leetcode.cn/problems/restore-ip-addresses/description/

在这里插入图片描述

public List<String> restoreIpAddresses(String s) {List<String> result = new ArrayList<>();List<String> path = new ArrayList<>();char[] charArray = s.toCharArray();backtrack(result, path, charArray, 0);return result;
}private void backtrack(List<String> result, List<String> path, char[] charArray, int startIndex) {if (startIndex == charArray.length && path.size() == 4) {// --if-- 字符串切割完成了,且符合ip形式,就存储ipStringBuilder ipSb = new StringBuilder();for (int i = 0; i < path.size() - 1; i++) {ipSb.append(path.get(i) + ".");}ipSb.append(path.get(path.size() - 1));result.add(ipSb.toString());return;} else if (path.size() > 4) {// --if-- 超过4个整数了,ip不合法return;}StringBuilder sb = new StringBuilder();for (int i = startIndex; i < charArray.length; i++) {sb.append(charArray[i]);// 判断子串是否为回文串,如果不是的,遍历下一个回文串if (isReasonable(sb.toString()) == false) {break;}path.add(sb.toString());backtrack(result, path, charArray, i + 1);path.remove(path.size() - 1);}
}private boolean isReasonable(String string) {// 不能含有前导 0if (string.length() > 1 && string.charAt(0) == '0') {return false;}// 每个整数位于 0 到 255 之间组成int num = Integer.parseInt(string);if (num > 255) {return false;}return true;
}

在这里插入图片描述

优化版本

统一使用一个StringBuilder

String temp;public List<String> restoreIpAddresses(String s) {List<String> result = new ArrayList<>();StringBuilder sb = new StringBuilder();backtrack(result, sb, s, 0, 0);return result;
}/*** 回溯方法** @param result     记录结果集* @param sb         记录当前ip* @param s          原字符串* @param startIndex 开始索引* @param splitNum   逗号分割数量*/
private void backtrack(List<String> result, StringBuilder sb, String s, int startIndex, int splitNum) {if (startIndex == s.length() && splitNum == 4) {// --if-- 字符串切割完成了,且符合ip形式,就存储ipresult.add(sb.toString());return;} else if (splitNum == 4) {// --if-- 超过4个整数了,ip不合法return;}for (int i = startIndex; i < s.length(); i++) {if (i - startIndex >= 1 && s.charAt(startIndex) == '0') {// --if-- 当前网段长度>1,且含有前导 0,不合法ip,breakbreak;}if (i - startIndex >= 3) {// --if-- 当前网段长度>3,不合法ip,breakbreak;}// 截取字符串作为网段,注意是左闭右开,所以i+1temp = s.substring(startIndex, i + 1);if (Integer.parseInt(temp) > 255) {// --if-- 当前网段值>255,不合法ip,breakbreak;}// 将当前网段添加到ip中sb.append(temp);// 如果逗号数量 < 3(一个ip,网段最多4个,逗号最多3个),拼接一个逗号if (splitNum < 3) {sb.append(".");}// 递归backtrack(result, sb, s, i + 1, splitNum + 1);// 回溯,删除最后一个网段,例如现在是255.255.11.13,回溯之后为 255.255.11.sb.delete(startIndex + splitNum, sb.length());}
}

在这里插入图片描述

子集

https://leetcode.cn/problems/subsets/description/

在这里插入图片描述

注意:题目说了nums中的所有元素各不相同,所以下面的代码没有必要去重

public List<List<Integer>> subsets(int[] nums) {List<List<Integer>> result = new ArrayList<>();List<Integer> path = new ArrayList<>();backtrack(result, path, nums, 0);return result;
}private void backtrack(List<List<Integer>> result, List<Integer> path, int[] nums, int startIndex) {// 任何集合都要存储,没有说需要满足什么条件才可以存储result.add(new ArrayList<>(path));// 当 startIndex = nums长度 的时候,本来需要return,但是下面的循环不会进行,所以无需把这部分代码写出来了for (int i = startIndex; i < nums.length; i++) {path.add(nums[i]);backtrack(result, path, nums, i + 1);path.remove(path.size() - 1);}
}

在这里插入图片描述

子集 II

https://leetcode.cn/problems/subsets-ii/description/

在这里插入图片描述

使用usedArr辅助去重

注意,这道题目和上面的不同,数组里面含有重复元素,需要去重操作,其实就和 组合总和II 的思路一样

public List<List<Integer>> subsetsWithDup(int[] nums) {List<List<Integer>> result = new ArrayList<>();List<Integer> path = new ArrayList<>();Arrays.sort(nums);int[] useArr = new int[nums.length];backtrack(result, path, nums, 0, useArr);return result;
}private void backtrack(List<List<Integer>> result, List<Integer> path, int[] nums, int startIndex, int[] useArr) {// 任何集合都要存储,没有说需要满足什么条件才可以存储result.add(new ArrayList<>(path));// 当 startIndex = nums长度 的时候,本来需要return,但是下面的循环不会进行,所以无需把这部分代码写出来了for (int i = startIndex; i < nums.length; i++) {// 去重if (i > 0 && useArr[i - 1] == 0 && nums[i - 1] == nums[i]) {continue;}path.add(nums[i]);useArr[i] = 1;backtrack(result, path, nums, i + 1, useArr);path.remove(path.size() - 1);useArr[i] = 0;}
}

在这里插入图片描述

不使用usedArr辅助去重

其实这道题还可以不用use数组

public List<List<Integer>> subsetsWithDup(int[] nums) {List<List<Integer>> result = new ArrayList<>();List<Integer> path = new ArrayList<>();Arrays.sort(nums);backtrack(result, path, nums, 0);return result;
}private void backtrack(List<List<Integer>> result, List<Integer> path, int[] nums, int startIndex) {// 任何集合都要存储,没有说需要满足什么条件才可以存储result.add(new ArrayList<>(path));// 当 startIndex = nums长度 的时候,本来需要return,但是下面的循环不会进行,所以无需把这部分代码写出来了for (int i = startIndex; i < nums.length; i++) {// 去重if (i > startIndex && nums[i - 1] == nums[i]) {continue;}path.add(nums[i]);backtrack(result, path, nums, i + 1);path.remove(path.size() - 1);}
}

注意,去重判断变成了如下代码。和上面代码的区别是一个为i > 0,一个为i > startIndex。以[1,2,2]为例子,当startIndex=2时。

  • 如果代码为i > 0,就会进入去重,导致子集[1,2,2]无法找到,只能找到[]、[1]、[1,2]。
  • 但是如果代码为i > startIndex,不会触发去重,因为i = startIndex
// 去重
if (i > startIndex && nums[i - 1] == nums[i]) {continue;
}

递增子序列※

在这里插入图片描述

public List<List<Integer>> findSubsequences(int[] nums) {List<List<Integer>> result = new ArrayList<>();List<Integer> path = new ArrayList<>();backtrack(result, path, nums, 0);return result;
}private void backtrack(List<List<Integer>> result, List<Integer> path, int[] nums, int startIndex) {// 任何集合都要存储,没有说需要满足什么条件才可以存储if (path.size() >= 2) {result.add(new ArrayList<>(path));}// 用来去重HashMap<Integer, Boolean> useMap = new HashMap<>();for (int i = startIndex; i < nums.length; i++) {// 如果不是递增,continueif (path.size() > 0 && nums[i] < path.get(path.size() - 1)) {continue;}// 去重,如果前面有用过相同的元素,直接continueif (useMap.getOrDefault(nums[i], false) == true) {continue;}useMap.put(nums[i], true);path.add(nums[i]);backtrack(result, path, nums, i + 1);path.remove(path.size() - 1);}
}

在这里插入图片描述

如果说数据为 [1, 2, 3, 4, 1, 1] ,不去重答案就会出问题
在这里插入图片描述

全排列

https://leetcode.cn/problems/permutations/

在这里插入图片描述

这道题只能暴力求解

public List<List<Integer>> permute(int[] nums) {List<List<Integer>> result = new ArrayList<>();List<Integer> path = new ArrayList<>();boolean[] useArr = new boolean[nums.length];backtrack(result, path, nums, useArr);return result;
}private void backtrack(List<List<Integer>> result, List<Integer> path, int[] nums, boolean[] useArr) {if (path.size() == nums.length) {result.add(new ArrayList<>(path));}for (int i = 0; i < nums.length; i++) {if (useArr[i] == true) {continue;}useArr[i] = true;path.add(nums[i]);backtrack(result, path, nums, useArr);useArr[i] = false;path.remove(path.size() - 1);}
}

在这里插入图片描述

不用数组useArr来辅助也可以,直接使用path的contain方法来判断元素是否已经存在即可

这道题和前面比较大的一个区别是不再需要startIndex这个变量,而是遍历整个数组,因为需要的所有排序

全排列 II

https://leetcode.cn/problems/permutations-ii/description/

在这里插入图片描述

public List<List<Integer>> permuteUnique(int[] nums) {List<List<Integer>> result = new ArrayList<>();List<Integer> path = new ArrayList<>();boolean[] useArr = new boolean[nums.length];// 升序排序Arrays.sort(nums);backtrack(result, path, nums, useArr);return result;
}private void backtrack(List<List<Integer>> result, List<Integer> path, int[] nums, boolean[] useArr) {if (path.size() == nums.length) {result.add(new ArrayList<>(path));}for (int i = 0; i < nums.length; i++) {if (useArr[i] == true) {continue;}// 去重,如果当前元素和前一个元素相同,而且useArr[i - 1] == false,前面一个元素肯定已经被同样的方式使用过,当前组合不需要再往下遍历了if (i > 0 && nums[i - 1] == nums[i] && useArr[i - 1] == false) {continue;}useArr[i] = true;path.add(nums[i]);backtrack(result, path, nums, useArr);useArr[i] = false;path.remove(path.size() - 1);}
}

在这里插入图片描述

重新安排行程

https://leetcode.cn/problems/reconstruct-itinerary/description/

在这里插入图片描述

在这里插入图片描述

题意

List<List<String>> tickets 中存储了多张票的起点和终点,我们要做的是:从起点JFK开始旅行,然后把每张票使用一遍,注意每张票只能使用一次

字典排序的意思如下,例如有两个字符串 ACD 和 ABE

  • 首先对比两个字符串的第一个字母,两者都是A打平手
  • 然后对比两个字符串的第二个字母,第一个字符串的字母为C,第二个字符串的字母为B,因为B在字母表中的顺序更加靠前,因此排序时ABE要排在ACD前面

代码

public List<String> findItinerary(List<List<String>> tickets) {List<String> result = new ArrayList<>();List<String> path = new ArrayList<>();// 存储开始站点 及 开始站点所对应的票 索引Map<String, List<Integer>> startStationAndIndexListMap = new HashMap<>();// 填充 startStationAndIndexListMap 的数据for (int i = 0; i < tickets.size(); i++) {List<String> ticket = tickets.get(i);if (!startStationAndIndexListMap.containsKey(ticket.get(0))) {ArrayList<Integer> indexList = new ArrayList<>();indexList.add(i);startStationAndIndexListMap.put(ticket.get(0), indexList);} else {startStationAndIndexListMap.get(ticket.get(0)).add(i);}}// 将indexList按照字典序升序排序for (Map.Entry<String, List<Integer>> entry : startStationAndIndexListMap.entrySet()) {List<Integer> indexList = entry.getValue();Collections.sort(indexList, ((o1, o2) -> {return compare(tickets.get(o1).get(1), tickets.get(o2).get(1));}));}// 用来标识哪张机票已经被使用过boolean[] useArr = new boolean[tickets.size()];// 起点是固定的,添加起点path.add("JFK");backtrack(tickets, result, path, startStationAndIndexListMap, useArr, 0, "JFK");return result;
}private void backtrack(List<List<String>> tickets, List<String> result, List<String> path, Map<String, List<Integer>> startStationAndIndexListMap, boolean[] useArr, int useTicketNum, String startStation) {if (useTicketNum == tickets.size() && result.size() == 0) {// --if-- 所有机票已经用完了,存储当前路径result.addAll(path);return;}List<Integer> indexList = startStationAndIndexListMap.get(startStation);if (indexList == null) {// 当前起点站没有机票了,直接退出return;}// 遍历从当前起始站出发的机票for (int i = 0; i < indexList.size(); i++) {if (result.size() > 0) {// --if-- 因为题目要求只要字典序最小的一条路径,因此找到一个结果就可以结束整个搜索了,这里直接returnreturn;}// 机票索引Integer ticketIndex = indexList.get(i);if (useArr[ticketIndex] == true) {// --if-- 该机票已经被使用过continue;}// 去重,当前机票首站-尾站和上一张机票的相同,直接跳过即可if (i > 0 && tickets.get(indexList.get(i - 1)).get(1).equals(tickets.get(indexList.get(i)).get(1)) && useArr[indexList.get(i - 1)] == false) {continue;}// 标识当前机票已经被使用useArr[ticketIndex] = true;path.add(tickets.get(ticketIndex).get(1));backtrack(tickets, result, path, startStationAndIndexListMap, useArr, useTicketNum + 1, tickets.get(ticketIndex).get(1));path.remove(path.size() - 1);useArr[ticketIndex] = false;}
}/*** 字典排序** @param str1* @param str2* @return*/
private int compare(String str1, String str2) {for (int i = 0; i < str1.length(); i++) {int compare = Integer.compare(str1.charAt(i), str2.charAt(i));if (compare != 0) {// --if-- 当前字母没有分出胜负,继续比较下一个字母return compare;}}return 0;
}

当然**return **compare(tickets.get(o1).get(1), tickets.get(o2).get(1));可以使用

return tickets.get(o1).get(1).compareTo(tickets.get(o2).get(1));来代替
在这里插入图片描述

小小优化了一下,去掉path,只使用result

public List<String> findItinerary(List<List<String>> tickets) {List<String> result = new ArrayList<>();// 存储开始站点 及 开始站点所对应的票 索引Map<String, List<Integer>> startStationAndIndexListMap = new HashMap<>();// 填充 startStationAndIndexListMap 的数据for (int i = 0; i < tickets.size(); i++) {List<String> ticket = tickets.get(i);if (!startStationAndIndexListMap.containsKey(ticket.get(0))) {ArrayList<Integer> indexList = new ArrayList<>();indexList.add(i);startStationAndIndexListMap.put(ticket.get(0), indexList);} else {startStationAndIndexListMap.get(ticket.get(0)).add(i);}}// 将indexList按照字典序升序排序for (Map.Entry<String, List<Integer>> entry : startStationAndIndexListMap.entrySet()) {List<Integer> indexList = entry.getValue();Collections.sort(indexList, ((o1, o2) -> {return tickets.get(o1).get(1).compareTo(tickets.get(o2).get(1));}));}// 用来标识哪张机票已经被使用过boolean[] useArr = new boolean[tickets.size()];// 起点是固定的,添加起点result.add("JFK");backtrack(tickets, result, startStationAndIndexListMap, useArr, "JFK");return result;
}private void backtrack(List<List<String>> tickets, List<String> result,  Map<String, List<Integer>> startStationAndIndexListMap, boolean[] useArr, String startStation) {if (result.size() == tickets.size() + 1) {// --if-- 所有机票已经用完了,存储当前路径return;}List<Integer> indexList = startStationAndIndexListMap.get(startStation);if (indexList == null) {// 当前起点站没有机票了,直接退出return;}// 遍历从当前起始站出发的机票for (int i = 0; i < indexList.size(); i++) {// 机票索引Integer ticketIndex = indexList.get(i);if (useArr[ticketIndex] == true) {// --if-- 该机票已经被使用过continue;}// 去重,当前机票首站-尾站和上一张机票的相同,直接跳过即可if (i > 0 && tickets.get(indexList.get(i - 1)).get(1).equals(tickets.get(ticketIndex).get(1)) && useArr[indexList.get(i - 1)] == false) {continue;}// 标识当前机票已经被使用useArr[ticketIndex] = true;result.add(tickets.get(ticketIndex).get(1));backtrack(tickets, result, startStationAndIndexListMap, useArr, tickets.get(ticketIndex).get(1));if (result.size() == tickets.size() + 1) {// --if-- 因为题目要求只要字典序最小的一条路径,因此找到一个结果就可以结束整个搜索了,这里直接returnreturn;}result.remove(result.size() - 1);useArr[ticketIndex] = false;}
}

在这里插入图片描述

字典直接存储一个站可以去往哪些站点,如果选择了该站点,将其从集合中删除,回溯的时候,再将该站点加回来

int totalStationNum;public List<String> findItinerary(List<List<String>> tickets) {totalStationNum = tickets.size() + 1;List<String> result = new ArrayList<>();// 存储开始站点 及 开始站点所对应的票 索引Map<String, List<String>> startStationAndEndStationListMap = new HashMap<>();// 填充 startStationAndIndexListMap 的数据for (int i = 0; i < tickets.size(); i++) {List<String> ticket = tickets.get(i);if (!startStationAndEndStationListMap.containsKey(ticket.get(0))) {List<String> endStationList = new ArrayList<>();endStationList.add(ticket.get(1));startStationAndEndStationListMap.put(ticket.get(0), endStationList);} else {startStationAndEndStationListMap.get(ticket.get(0)).add(ticket.get(1));}}// 将indexList按照字典序升序排序for (Map.Entry<String, List<String>> entry : startStationAndEndStationListMap.entrySet()) {Collections.sort(entry.getValue(), ((o1, o2) -> {return o1.compareTo(o2);}));}// 起点是固定的,添加起点result.add("JFK");backtrack(result, startStationAndEndStationListMap, "JFK");return result;
}private boolean backtrack(List<String> result, Map<String, List<String>> startStationAndEndStationListMap, String startStation) {if (result.size() == totalStationNum) {// --if-- 所有机票已经用完了,存储当前路径return true;}List<String> endStationList = startStationAndEndStationListMap.get(startStation);if (endStationList == null) {// 当前起点站没有机票了,直接退出return false;}// 遍历从当前起始站出发的机票int i = 0;while (i < endStationList.size()) {// 目标站点String endStation = endStationList.get(i);// 去重,当前机票首站-尾站和上一张机票的相同,直接跳过即可if (i > 0 && endStation.equals(endStationList.get(i - 1))) {i++;continue;}// 去往该站点,将其从集合中删除endStationList.remove(i);result.add(endStation);if (backtrack(result, startStationAndEndStationListMap, endStation) == true) {// --if-- 因为题目要求只要字典序最小的一条路径,因此找到一个结果就可以结束整个搜索了,这里直接returnreturn true;}result.remove(result.size() - 1);endStationList.add(i, endStation);i++;}return false;
}

在这里插入图片描述

代码随想录的代码效率更高

N 皇后

https://leetcode.cn/problems/n-queens/description/

在这里插入图片描述

思路:遍历每一行,然后看当行的每列,再判断当前位置是否可行

public List<List<String>> solveNQueens(int n) {List<List<String>> result = new ArrayList<>();// 初始化棋盘char[][] chessboard = new char[n][n];for (int i = 0; i < n; i++) {Arrays.fill(chessboard[i], '.');}backtrack(result, chessboard, 0, n);return result;
}private void backtrack(List<List<String>> result, char[][] chessboard, int layer, int n) {if (layer == n) {// --for-- 遍历到最后一行了,保存结果就结束List<String> path = new ArrayList<>();for (int i = 0; i < chessboard.length; i++) {path.add(new String(chessboard[i]));}result.add(path);}for (int j = 0; j < n; j++) {// --for-- 遍历棋盘中当前行的每一列boolean isCanPlace = true;if (layer > 0) {// --if-- 如果是第二行以上,需要判断上面行有没有皇后排斥。第一行不需要看for (int i = 0; i < layer; i++) {// 判断同列是否有皇后if (chessboard[i][j] == 'Q') {isCanPlace = false;break;}// 判断斜对角是否有皇后if (((j - (layer - i)) >= 0 && chessboard[i][j - (layer - i)] == 'Q') ||(j + (layer - i) < n && chessboard[i][j + (layer - i)] == 'Q')) {isCanPlace = false;break;}}}if (isCanPlace == true) {// --if-- 当前位置可以放皇后chessboard[layer][j] = 'Q';backtrack(result, chessboard, layer + 1, n);chessboard[layer][j] = '.';}}
}

在这里插入图片描述

代码随想录中有更快的方法

解数独

https://leetcode.cn/problems/sudoku-solver/description/

在这里插入图片描述

在这里插入图片描述

直接遍历

一个单元格一个单元格的遍历,然后遍历[1,9]的每个数字,判断该数字是否可设置。如果所有数字都不可以设置,回溯

char[] selectNumCharArr = new char[]{'1', '2', '3', '4', '5', '6', '7', '8', '9'};
int cellRowIndex;
int cellColumnIndex;
int cellRowIndexStart;
int cellColumnIndexStart;
int cellRowIndexEnd;
int cellColumnIndexEnd;
boolean reachEnd;
boolean isFindResult;
boolean isExit;public void solveSudoku(char[][] board) {backtrack(0, 0, board);
}/*** 思路:依次遍历每一个格子来填充数字** @param rowIndex* @param columnIndex* @param board* @return*/
private boolean backtrack(int rowIndex, int columnIndex, char[][] board) {if (rowIndex == board.length) {// --if-- 所有行都已经遍历完成,说明全部数字填充完成return true;}if (board[rowIndex][columnIndex] != '.') {// --if-- 当前格子已经有数字// 判断当前行的每个格子是否遍历完成reachEnd = (columnIndex + 1 == board.length);// 如果当前行遍历完成,就要换行,columnIndex从0开始isFindResult = backtrack(reachEnd ? rowIndex + 1 : rowIndex, reachEnd ? 0 : columnIndex + 1, board);if (isFindResult) {// --if-- 找到结果,直接返回return true;}} else {for (int k = 0; k < selectNumCharArr.length; k++) {char numChar = selectNumCharArr[k];// 检测同一行是否有重复值isExit = false;for (int j = 0; j < board.length; j++) {if (board[rowIndex][j] == numChar) {isExit = true;break;}}if (isExit) {continue;}// 检测同一列是否有重复值for (int i = 0; i < board.length; i++) {if (board[i][columnIndex] == numChar) {isExit = true;break;}}if (isExit) {continue;}// 检测框里面是否有重复值cellRowIndex = rowIndex / 3;cellColumnIndex = columnIndex / 3;cellRowIndexStart = cellRowIndex * 3;cellColumnIndexStart = cellColumnIndex * 3;cellRowIndexEnd = (cellRowIndex + 1) * 3;cellColumnIndexEnd = (cellColumnIndex + 1) * 3;for (int i = cellRowIndexStart; i < cellRowIndexEnd; i++) {for (int j = cellColumnIndexStart; j < cellColumnIndexEnd; j++) {if (board[i][j] == numChar) {isExit = true;break;}}if (isExit) {break;}}if (isExit) {continue;}// 通过上述校验,可以放下当前数字board[rowIndex][columnIndex] = numChar;// 递归到下一个格子reachEnd = (columnIndex + 1 == board.length);isFindResult = backtrack(reachEnd ? rowIndex + 1 : rowIndex, reachEnd ? 0 : columnIndex + 1, board);if (isFindResult) {return true;}// 回溯board[rowIndex][columnIndex] = '.';}}return false;
}

在这里插入图片描述

优化

使用boolean数组去重,减少行、列、单元格的循环判断

char[] selectNumCharArr = new char[]{'1', '2', '3', '4', '5', '6', '7', '8', '9'};
boolean reachEnd;
boolean isFindResult;
// 行去重
boolean[][] rowUsedArr = new boolean[9][9];
// 列去重
boolean[][] columnUsedArr = new boolean[9][9];
// 单元去重
boolean[][] cellUsedArr = new boolean[9][9];public void solveSudoku(char[][] board) {int num;for (int i = 0; i < board.length; i++) {for (int j = 0; j < board.length; j++) {if (board[i][j] != '.') {num = board[i][j] - '1';rowUsedArr[i][num] = true;columnUsedArr[j][num] = true;cellUsedArr[(i / 3) * 3 + (j / 3)][num] = true;}}}backtrack(0, 0, board);
}/*** 思路:依次遍历每一个格子来填充数字** @param rowIndex* @param columnIndex* @param board* @return*/
private boolean backtrack(int rowIndex, int columnIndex, char[][] board) {if (rowIndex == board.length) {// --if-- 所有行都已经遍历完成,说明全部数字填充完成return true;}if (board[rowIndex][columnIndex] != '.') {// --if-- 当前格子已经有数字// 判断当前行的每个格子是否遍历完成reachEnd = (columnIndex + 1 == board.length);// 如果当前行遍历完成,就要换行,columnIndex从0开始isFindResult = backtrack(reachEnd ? rowIndex + 1 : rowIndex, reachEnd ? 0 : columnIndex + 1, board);if (isFindResult) {// --if-- 找到结果,直接返回return true;}} else {for (int k = 0; k < selectNumCharArr.length; k++) {int num = selectNumCharArr[k] - '1';// 检测同一行是否有重复值if (rowUsedArr[rowIndex][num]) {continue;}// 检测同一列是否有重复值if (columnUsedArr[columnIndex][num] == true) {continue;}// 检测框里面是否有重复值int cellIndex = (rowIndex / 3) * 3 + (columnIndex / 3);if (cellUsedArr[cellIndex][num]) {continue;}// 通过上述校验,可以放下当前数字board[rowIndex][columnIndex] = selectNumCharArr[k];rowUsedArr[rowIndex][num] = true;columnUsedArr[columnIndex][num] = true;cellUsedArr[cellIndex][num] = true;// 递归到下一个格子reachEnd = (columnIndex + 1 == board.length);isFindResult = backtrack(reachEnd ? rowIndex + 1 : rowIndex, reachEnd ? 0 : columnIndex + 1, board);if (isFindResult) {return true;}// 回溯board[rowIndex][columnIndex] = '.';rowUsedArr[rowIndex][num] = false;columnUsedArr[columnIndex][num] = false;cellUsedArr[cellIndex][num] = false;}}return false;
}

在这里插入图片描述

相关文章:

《代码随想录》刷题笔记——回溯篇【java实现】

文章目录 组合组合总和 III电话号码的字母组合组合总和组合总和II思路代码实现 分割回文串※思路字符串分割回文串判断效率优化※ 复原 IP 地址优化版本 子集子集 II使用usedArr辅助去重不使用usedArr辅助去重 递增子序列※全排列全排列 II重新安排行程题意代码 N 皇后解数独直…...

React:初识React

React是什么&#xff1f; React是由Meta公司研发&#xff0c;也就是Facebook的公司&#xff08;马克扎克伯格这个见人&#xff09;研发的构建Web和原生交互界面的库 不仅可以写网页&#xff0c;还可以写苹果和安卓上面的app React的优势&#xff1a; React也是前端里最流行的…...

全面理解-c++中的内存布局

在 C 中&#xff0c;程序的内存布局指的是程序运行时&#xff0c;代码和数据在内存中的组织和分布方式。一般来说&#xff0c;C 程序的内存可以划分为以下几个主要区域&#xff1a; 1. 代码段&#xff08;Text Segment&#xff0c;也称为 .text 段&#xff09; 存储内容&…...

百度沈抖:传统云计算不再是主角,智能计算呼唤新一代“操作系统”

Create 2024 百度AI开发者大会 4月16日&#xff0c;Create 2024 百度AI开发者大会在深圳召开。期间&#xff0c;百度集团执行副总裁、百度智能云事业群总裁沈抖正式发布新一代智能计算操作系统——万源&#xff0c;通过对AI原生时代的智能计算平台进行抽象与封装设计&#xff…...

【银河麒麟高级服务器操作系统】服务器卡死后恢复系统日志丢失-分析及处理全过程

了解更多银河麒麟操作系统全新产品&#xff0c;请点击访问 麒麟软件产品专区&#xff1a;https://product.kylinos.cn 开发者专区&#xff1a;https://developer.kylinos.cn 文档中心&#xff1a;https://document.kylinos.cn 服务器环境以及配置 【机型】 处理器&#xff…...

VSCode Error Lens插件介绍(代码静态检查与提示工具)(vscode插件)

文章目录 VSCode Error Lens 插件介绍**功能概述****开发背景****使用方法****适用场景** VSCode Error Lens 插件介绍 功能概述 Error Lens 是一款增强 VS Code 错误提示的扩展工具&#xff0c;通过 内联显示错误和警告信息&#xff0c;直接定位代码问题&#xff0c;提升开发…...

ffmpeg configure 研究1-命令行参数的分析

author: hjjdebug date: 2025年 02月 14日 星期五 17:16:12 CST description: ffmpeg configure 研究1 ./configure 命令行参数的分析 文章目录 1 configure 对命令行参数的分析,在4019行1.1 函数名称: is_in1.2. 函数名称: enable1.3. 函数名称: set_all 2 执行退出判断的关键…...

如何调整 Nginx工作进程数以提升性能

&#x1f3e1;作者主页&#xff1a;点击&#xff01; Nginx-从零开始的服务器之旅专栏&#xff1a;点击&#xff01; &#x1f427;Linux高级管理防护和群集专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2025年2月15日14点20分 Nginx 的工作进程数&#xff0…...

分布式 NewSQL 数据库(TiDB)

TiDB 是一个分布式 NewSQL 数据库。它支持水平弹性扩展、ACID 事务、标准 SQL、MySQL 语法和 MySQL 协议&#xff0c;具有数据强一致的高可用特性&#xff0c;是一个不仅适合 OLTP 场景还适合 OLAP 场景的混合数据库。 TiDB是 PingCAP公司自主设计、研发的开源分布式关系型数据…...

try learning-git-branching

文章目录 mergerebase分离 HEAD相对引用利用父节点branch -f 撤销变更cherry-pick交互式 rebase只取一个提交记录提交的技巧rebase 在上一次提交上amendcherry-pick 在上一次提交上 amend tag多分支 rebase两个parent节点纠缠不清的分支偏离的提交历史锁定的Main推送主分支合并…...

【kafka系列】Kafka事务的实现原理

目录 1. 事务核心组件 1.1 幂等性生产者&#xff08;Idempotent Producer&#xff09; 1.2 事务协调器&#xff08;TransactionCoordinator&#xff09; 1.3 事务日志&#xff08;Transaction Log&#xff09; 2. 事务执行流程 2.1 事务初始化 2.2 发送消息 2.3 事务提…...

数据结构6

一、哈希散列--通讯录查找 #include "hash.h" #include <stdio.h> #include <stdlib.h> #include <string.h>//int *a[10];int hash_function(char key) {if (key > a && key < z){return key - a;}else if (key > A && …...

Flutter 的 Widget Key 提议大调整?深入聊一聊 Key 的作用

Flutter 的 Widget Key 提议大调整&#xff1f;深入聊一聊 Key 的作用 在 Flutter 里&#xff0c;Key 对象存在的目的主要是区分和维持 Widget 的状态&#xff0c;它是控件在渲染树里的「复用」标识之一&#xff0c;这一点在之前的《深入 Flutter 和 Compose 在 UI 渲染刷新时…...

src和href区别

src和href区别 (1)请求资源类型不同(2)作用结果不同(3)解析方式不同 (1)请求资源类型不同 href 用来建立文档和元素之间的链接(是引用),常用的有a、linksrc 在请求src资源时候会将指向的资源下载并且应用到文档中(引入),常用的有script、iframe、image。 (2)作用结果不同 hr…...

STM32之SG90舵机控制

目录 前言&#xff1a; 一、硬件准备与接线 1.1 硬件清单 1.2 接线 二、 SG90舵机简介 1.1 外观 1.2 基本参数 1.3 引脚说明 1.4 控制原理 1.5 特点 1.6 常见问题 三、 单片机简介 四、 程序设计 4.1 定时器配置 4.2 角度控制函数 4.3 主函数调用 五、 总结 …...

尚硅谷课程【笔记】——大数据之Hadoop【一】

课程视频链接&#xff1a;尚硅谷Hadoop3.x教程 一、大数据概论 1&#xff09;大数据概念 大数据&#xff08;Big Data&#xff09;&#xff1a;指无法再一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合&#xff0c;是需要新处理模式才能具有更强的决策力、洞察发…...

QEMU 搭建 Ubuntu x86 虚拟机

1. 安装 QEMU 在 Ubuntu 系统中&#xff0c;可以通过以下命令安装 QEMU&#xff1a; sudo apt-get update sudo apt-get install qemu-system-x86_64 qemu-kvm libvirt-daemon libvirt-clients bridge-utils virt-manager2. 创建虚拟硬盘镜像 qemu-img create -f raw ubuntu…...

mac 意外退出移动硬盘后再次插入移动硬盘不显示怎么办

第一步&#xff1a;sudo ps aux | grep fsck 打开mac控制台输入如下指令&#xff0c;我们看到会出现两个进程&#xff0c;看进程是root的这个 sudo ps aux|grep fsck 第二步&#xff1a;杀死进程 在第一步基础上我们知道不显示u盘的进程是&#xff1a;62319&#xff0c;我们…...

Acwing-基础算法课笔记之基础算法(双指针)

Acwing-基础算法课笔记之基础算法&#xff08;双指针&#xff09; 一、双指针算法概念二、关于双指针的一个问题三、模板 一、双指针算法概念 双指针&#xff08;又称尺取法&#xff09;是一个常用的优化技巧&#xff0c;用来解决序列的区间问题。 两个指针i&#xff0c;j&am…...

PCIE基础学习

PCIE PIO模式&#xff1a; 一个CPU传输一个32bit给PCIE&#xff08;IP&#xff09;。CPU直接与PCIE做数据传输。 DMA模式&#xff1a; CPU通过PCIE bridge 与多个PCIE设备连接&#xff0c;CPU发送命令给桥&#xff0c;桥控制PCIE与memory直接数据连接。 tlp报文 读报文 …...

架构——Nginx功能、职责、原理、配置示例、应用场景

以下是关于 Nginx 的功能、职责、原理、配置示例、应用场景及其高性能原因的详细说明&#xff1a; 一、Nginx 的核心功能 1. 静态资源服务 功能&#xff1a;直接返回静态文件&#xff08;如 HTML、CSS、JS、图片、视频等&#xff09;。配置示例&#xff1a;server {listen 80…...

【教程】比亚迪车机接入AI大模型语音助手

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 更新说明&#xff1a; v1.1.0.2 1、新增长按音量键触发&#xff0c;不再需要迪加 (需设置modelisten)。 2、新增kimi、豆包、ChatGPT等多个GPT接口。 3…...

ios中常见的设计原则和设计模式

七大设计原则 1&#xff1a;开闭原则 对扩展开放&#xff0c;对修改关闭&#xff0c;在设计模块的时候&#xff0c;使模块在不被修改的前提下可以扩展功能 2:依赖倒置原则 实现尽量依赖抽象&#xff0c;不依赖具体实现 &#xff08;1&#xff09;高层模块不应该依赖底层模…...

WSL Ubuntu 安装 CUDA 教程

WSL Ubuntu 安装 CUDA 教程 1. 概述2. 准备工作3. 删除旧的 GPG 密钥4. 安装 CUDA Toolkit4.1 使用 WSL-Ubuntu 包安装&#xff08;推荐&#xff09; 5. 设置环境变量6. 注意事项7. 参考链接8. 总结 1. 概述 随着 WSL 2 的推出&#xff0c;Windows 用户现在可以在 Windows 子系…...

案例-02.部门管理-查询

一.查询部门-需求 二.查询部门-思路 API接口文档 三.代码实现 1.controller层&#xff1a;负责与前端进行交互&#xff0c;接收前端所发来的请求 注&#xff1a;Slf4j用于记录日志使用&#xff0c;可以省略private static Logger log LoggerFactory.getLogger(DeptControlle…...

【ARM】解决ArmDS Fast Models 中部分内核无法上电的问题

1、 文档目标 解决ArmDS Fast Models 中部分内核无法上电的问题。 2、 问题场景 在调用ArmDS的Fast Models中的Cortex-A55的模型&#xff0c;只有Core 0是上电状态&#xff0c;而Core 1处于掉电状态&#xff0c;如图2-1所示&#xff1a; 图2-1 3、软硬件环境 1&#xff09;…...

docker 基础命令使用(ubuntu)

docker 状态查询 docker ps docker ps -adocker --version docker info docker --help docker run --help docker ps --help ...docker 操作镜像命令 docker imagesdocker rmi 镜像id/镜像名docker 操作容器命令 docker ps docker ps -adocker run 命令 # 端口映射 -p 参数…...

WEB安全--SQL注入--二次注入

一、原理&#xff1a; 二次注入的关键在于攻击者的输入并不立即执行&#xff0c;而是经过某些存储或处理后&#xff0c;在后续某个步骤中再触发注入攻击 二、示例&#xff1a; 2.1、sqli-labs-master/less-24&#xff1a; admin# 第一次在网页注册账号和密码时没有漏洞&#x…...

c++中什么时候应该使用final关键字?

在C中&#xff0c;final关键字是自C11标准引入的重要特性&#xff0c;主要用于类继承和虚函数重写机制的约束。下面从技术原理、使用场景和最佳实践三个维度进行系统分析&#xff0c;并给出工业级代码示例。 目录 一、技术原理深度解析 二、关键使用场景分析 1. 类级别的fi…...

DeepSeek学术秘籍:如何让DeepSeek辅助论证?

随着人工智能技术的飞速发展&#xff0c;AIGC技术在学术领域的应用逐渐引起了广泛关注。其中最近大火的DeepSeek作为一款基于大语言模型的应用&#xff0c;其出现标志着学术论文写作中研究方法的一次重大变革。 辅助论证 在学术论文写作中&#xff0c;借助DeepSeek优化辅助论证…...

Atlassian工具集:Jira与Confluence集成优势、使用技巧、更新功能等

本文由Atlassian全球白金合作伙伴-龙智翻译整理&#xff0c;深入探讨了Jira和Confluence最受欢迎的集成功能与技巧&#xff0c;期待为您新一年的团队协作开个好头。 此前&#xff0c;来自K15t 的Customer Advocate Matt Reiner 和Atlassian副产品经理David Olive在一场学习会议…...

传输层协议TCP ( 下 )

文章目录 前言序号与确认序号超时重传RTOJacobson算法内核中超时时间的计算 滑动窗口滑动窗口延迟应答流量控制 拥塞控制慢启动拥塞避免快重传快速恢复 保活机制参考资料 前言 TCP&#xff08;Transmission Control Protocol&#xff0c;传输控制协议&#xff09;是互联网最重要…...

【Deepseek 零门槛指南】DeepSeek 教程和常见问题解答 | 大白技术控

粉丝朋友们大家好&#xff0c;我是极客学长。最近一直在玩 DeepSeek&#xff0c;积累了一点经验&#xff0c;用它提高写作的效率挺好用的。 在使用DeepSeek的过程中&#xff0c;也遇到了如下几个问题(相信很多小伙伴也遇到了)&#xff1a; DeepSeek 官网卡顿&#xff0c;突然出…...

ELK组成及实现原理

ELK是由三个主要组件组成的日志处理和搜索平台&#xff0c;分别是&#xff1a; Elasticsearch&#xff1a;Elasticsearch 是一个基于Lucene构建的开源搜索引擎&#xff0c;提供强大的搜索、分析功能。它负责存储和索引所有数据&#xff0c;并提供实时搜索能力。数据可以通过HTT…...

迅为RK3568开发板篇OpenHarmony实操HDF驱动配置LED-LED测试

将编译好的镜像全部进行烧写&#xff0c;镜像在源码根目录 out/rk3568/packages/phone/images/目录下。 烧写完成之后&#xff0c;在调试串口查看打印日志&#xff0c;如下图所示&#xff1a; 然后打开 hdc 工具&#xff0c;运行测试程序&#xff0c;输入“led_test 1”&…...

【C++】IO流

目录 一、C语言的输入与输出二、流是什么三、CIO流3.1 C标准IO流3.2 C文件IO流3.2.1 二进制读写3.2.2 文本读写 四、stringstream的简单介绍结尾 一、C语言的输入与输出 C语言中我们用到的最频繁的输入输出方式就是scanf ()与printf()。 scanf(): 从标准输入设备(键盘)读取数据…...

前端知识速记--css篇:CSS3中的常见动画及实现方式

前端知识速记–css篇&#xff1a;CSS3中的常见动画及实现方式 常见的CSS3动画 1. 过渡 (Transitions) 过渡是一种非常简单的动画效果&#xff0c;允许你在元素的状态变更时平滑过渡到新状态。 语法格式&#xff1a; transition: property duration timing-function delay;…...

一个根据输入内容过滤下拉选的组件

1.element的select自定义过滤不是很灵&#xff0c;使用了input和dropdown 组件 <template><div class"autocomplete-wrapper"><!-- 使用 el-input 组件 --><el-inputv-model"inputValue"input"handleInput"placeholder&q…...

Java中的分布式(概念说明)

1. 分布式的基本概念 1.1 什么是分布式系统&#xff1f; 分布式系统&#xff08;Distributed System&#xff09;&#xff1a;由多台服务器&#xff08;或节点&#xff09;协同工作&#xff0c;对外提供一个整体服务。不同节点之间通过网络通信来协同处理请求或共享数据&…...

国产编辑器EverEdit - 上下翻滚不迷路(历史编辑位置、历史光标位置回溯功能)

1 光标位置跳转 1.1 应用场景 某些场景下&#xff0c;用户从当前编辑位置跳转到别的位置查阅信息&#xff0c;如果要快速跳转回之前编辑位置&#xff0c;则可以使用光标跳转相关功能。 1.2 使用方法 1.2.1 上一个编辑位置 跳转到上一个编辑位置&#xff0c;即文本修改过的位…...

css简介

一.css-网页的美容师 css也是一种标记语言&#xff0c;主要用于设置HTML页面中的文本内容(字体大小对齐方式)&#xff0c;图片外形&#xff08;宽高 边框样式 边距等&#xff09;以及版面的布局和外观显示样式。 二.css语法规范 css规则由两个主要的部分构成:选择器以及一条…...

GoC题解(21) 725.画迷宫(下册第4课)

题目描述 真观察下面迷宫图。发现它是一个边长逐渐变长的15边回旋图&#xff0c;边长依次为10、20、30....。 参考答案 int main(){int len0;for(int i1;i<15;i){ len10;pen.fd(len).rt(90); }return 0; } 解题思路 使用一个变量来记录每次循环时应该画多长的边&#…...

DDD该怎么去落地实现(3)通用的仓库和工厂

通用的仓库和工厂 我有一个梦&#xff0c;就是希望DDD能够成为今后软件研发的主流&#xff0c;越来越多研发团队都转型DDD&#xff0c;采用DDD的设计思想和方法&#xff0c;设计开发软件系统。这个梦想在不久的将来是有可能达成的&#xff0c;因为DDD是软件复杂性的解决之道&a…...

sql sqlserver的特殊函数COALESCE和PIVOT的用法分析

一、COALESCE是一个返回参数中第一个非NULL值的函数&#xff0c; 列如&#xff1a;COALESCE&#xff08;a,b,c,d,e&#xff09;;可以按照顺序取abcde&#xff0c;中的第一个非空数据&#xff0c;abcde可以是表达式 用case when 加ISNULL也可以实现&#xff0c;但是写法复杂了…...

FPGA简介|结构、组成和应用

Field Programmable Gate Arrays&#xff08;FPGA&#xff0c;现场可编程逻辑门阵列&#xff09;&#xff0c;是在PAL、GAL、CPLD等可编程器件的基础上进一步发展的产物&#xff0c; 是作为专用集成电路&#xff08;ASIC&#xff09;领域中的一种半定制电路而出现的&#xff0c…...

Vue2/Vue3生命周期对比

Vue2的生命周期钩子 beforeCreate 在实例初始化之后&#xff0c;数据观测&#xff08;data&#xff09;和事件配置之前调用。此时无法访问 data、methods 等。 created 在实例创建完成后调用。此时可以访问 data、methods&#xff0c;但 DOM 还未生成。 beforeMount 在挂载…...

Spring Boot 携手 DeepSeek:开启智能交互新时代

前言 在当今数字化浪潮汹涌澎湃的时代,人工智能技术正以前所未有的速度改变着我们的生活和工作方式。大语言模型作为人工智能领域的一颗璀璨明星,凭借其强大的自然语言处理能力,为各个行业带来了新的发展机遇。DeepSeek 作为一款性能卓越的大语言模型,以其高效、准确的文本…...

【Elasticsearch】Mapping概述

以下是Elasticsearch中提到的关于Mapping的各模块概述&#xff1a; --- 1.Dynamic mapping&#xff08;动态映射&#xff09; 动态映射是指Elasticsearch在索引文档时&#xff0c;自动检测字段类型并创建字段映射的过程。当你首次索引一个文档时&#xff0c;Elasticsearch会根…...

国内Ubuntu离线安装和配置Ollama服务

以下是在 Ubuntu 22.04 系统上&#xff0c;安装Ollama 的完整安装和配置步骤&#xff1a; 1. 准备工作 确保你具备 root 权限&#xff0c;并安装了必要的工具&#xff0c;如 tar、systemctl 等。 2. 创建 Ollama 用户和组 创建一个专门的 ollama 用户和组来运行 Ollama 服务…...

极狐GitLab 17.8 正式发布,多项 DevOps 重点功能解读【二】

GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 学习极狐GitLab 的相关资料&#xff1a; 极狐GitLab 官网极狐…...