回溯——分割回文串
题目
思路
问题拆解
-
回文串定义:回文串是指从前往后和从后往前读都是相同的字符串。例如,"aba" 和 "racecar" 都是回文串。
-
递归 + 回溯思想:我们可以逐步检查字符串的每一个前缀是否是回文串,如果是回文串,则将其作为一个可能的子串继续进行递归,检查剩余部分字符串的可能性。我们需要将所有可能的分割方案保存下来。
-
回溯:递归中,当某一分支无法满足条件时,我们需要回溯,取消之前的选择,尝试其他分割方式。
实现思路
- 从字符串的第一个字符开始,逐步检查每一个前缀是否是回文串。
- 如果是回文串,则将该前缀作为一个可能的子串,并递归处理剩余的字符串。
- 当字符串被完全分割(即没有剩余字符串时),将该分割方案保存起来。
- 使用回溯来尝试不同的分割方案。
步骤
- 判断回文:首先我们需要一个辅助函数来判断某个字符串的某个区间是否为回文串。
- 递归与回溯:我们需要递归遍历字符串的每个前缀,并尝试将其作为一个回文串。
- 终止条件:当遍历到字符串的末尾时,说明当前路径已经找到一个可行的分割方案,将其加入结果中。
代码
public List<List<String>> partition(String s) {
List<List<String>> res = new ArrayList<List<String>>();
if(s == null || s.isEmpty())
return res;
if(s.length()==1){
ArrayList<String> strings = new ArrayList<String>();
strings.add(s);
res.add(strings);
}
List<String> stringList = new ArrayList<>();
fun(s,stringList,res,0);
return res;
}
private void fun(String s, List<String> stringList, List<List<String>> res, int start) {
if(start == s.length()){
List<String> now = new ArrayList<>(stringList);
res.add(now);
}
for(int i=start;i<s.length();i++){
String str = s.substring(start,i+1);
if(check(str)){
stringList.add(str);
fun(s,stringList,res,i+1);
stringList.removeLast();
}
}
}
private boolean check(String s) {
char[] charArray = s.toCharArray();
int i=0,j=s.length()-1;
while(i<j){
if(charArray[i]!=charArray[j]){
return false;
}
i++;
j--;
}
return true;
}
public static void main(String[] args) {
test0910 test = new test0910();
System.out.println(test.partition("aabcd"));
}
回溯——N皇后
题目
思路
-
基本思想:
- 回溯法:N 皇后问题的本质是一个排列组合问题。通过递归与回溯的方式,可以在棋盘的每一行中放置一个皇后,并检查是否满足约束条件(没有冲突)。一旦发现一个合法的解,就将它加入结果集中。
- 逐行放置:我们在每一行尝试放置皇后,然后检查它是否与之前放置的皇后冲突。如果不冲突,则继续放置下一行的皇后;如果发生冲突,就回溯到上一个位置,重新选择。
-
冲突检测:
- 行:每行只能放一个皇后,因此我们只需要在每一行选择一个位置。
- 列:通过一个布尔数组
row[i]
来标记第i
列是否已经放置过皇后。 - 主对角线:主对角线上的元素坐标差(
x - y
)是相等的。我们可以使用一个布尔数组djx1[x - y + n - 1]
来标记主对角线是否被占用。 - 副对角线:副对角线上的元素坐标和(
x + y
)是相等的。我们可以使用另一个布尔数组djx2[x + y]
来标记副对角线是否被占用。
-
递归与回溯:
- 在每一行放置一个皇后时,首先判断当前列和对角线是否已经被占用。
- 如果合法,则放置皇后,并递归处理下一行。
- 如果不合法或递归失败,则回溯到上一行,尝试其他可能的位置。
- 一旦某一行的所有位置都尝试过且没有成功,则进行回溯,撤销上一行的皇后放置,继续尝试其他位置。
-
状态保存:
- 使用一个数组
queen[]
来记录每一行皇后放置的位置。例如,queen[i] = j
表示在第i
行的第j
列放置了皇后。 - 通过
queen[]
数组可以方便地生成最终的棋盘布局。
- 使用一个数组
代码
public List<List<String>> solveNQueens(int n) {
List<List<String>> res = new ArrayList<>();
int[] queen = new int[n];
boolean[] row = new boolean[n];
boolean[] djx1 = new boolean[2*n-1];
boolean[] djx2 = new boolean[2*n-1];
fun3(res,queen,row,djx1,djx2,0,n);
return res;
}
private void fun3(List<List<String>> res,int[] queen, boolean[] row, boolean[] djx1, boolean[] djx2, int x, int n) {
if(x==n){
res.add(trans(queen,n));
return;
}
for(int i=0;i<n;i++){
if(row[i]||djx1[x-i+n-1]||djx2[i+x]){
continue;
}
row[i] = true;
queen[x]=i;
djx1[x-i+n-1]=true;
djx2[i+x]=true;
fun3(res,queen,row,djx1,djx2,x+1,n);
row[i]=false;
djx1[x-i+n-1]=false;
djx2[i+x]=false;
}
}
private List<String> trans(int[] queen, int n) {
List<String> board = new ArrayList<>();
for (int i = 0; i < n; i++) {
char[] row = new char[n];
for (int j = 0; j < n; j++) {
row[j] = '.'; // 初始为空位
}
row[queen[i]] = 'Q'; // 将皇后放置在第 i 行的正确列
board.add(new String(row)); // 将当前行加入到棋盘中
}
return board;
}
二分查找——寻找两个正序数组的中位数
题目
4. 寻找两个正序数组的中位数 - 力扣(LeetCode)
思路
对数组进行合并然后找出中位数
但是这样时间复杂度会来到O(m+n)
优化后的思路
为了找到两个有序数组的中位数,我们不需要合并两个数组。可以通过二分查找来确定两个数组的中位数,利用其有序性高效解决
代码
简单代码
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int n = nums1.length+nums2.length;
int[] nums = new int[n];
int k=0,m=0;
for(int i=0;i<n;i++){
if(k==nums1.length){
nums[i]=nums2[m];
m++;
continue;
}
if(m==nums2.length){
nums[i]=nums1[k];
k++;
continue;
}
if(k<nums1.length&&m< nums2.length&&nums1[k]<nums2[m]){
nums[i] = nums1[k];
k++;
}else if(k<nums1.length&&m< nums2.length&&nums2[m]<=nums1[k]){
nums[i] = nums2[m];
m++;
}
}
double result = 0.0;
if(n%2==0){
result = ((double) nums[n/2]+(double) nums[n/2-1])/2;
}else{
result = (double) nums[n/2];
}
return result;
}
优化后的代码
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
// 确保 nums1 是较短的数组
if (nums1.length > nums2.length) {
return findMedianSortedArrays(nums2, nums1);
}
//计算总的左侧元素个数
int m = nums1.length;
int n = nums2.length;
int totalLeft = (m + n + 1) / 2;
//二分查找
int left = 0, right = m;
while (left < right) {
int i = (left + right + 1) / 2;
int j = totalLeft - i;
if (nums1[i - 1] > nums2[j]) {
right = i - 1;
} else {
left = i;
}
}
int i = left;
int j = totalLeft - i;
//分割点处的情况分析
int nums1LeftMax = (i == 0) ? Integer.MIN_VALUE : nums1[i - 1];
int nums1RightMin = (i == m) ? Integer.MAX_VALUE : nums1[i];
int nums2LeftMax = (j == 0) ? Integer.MIN_VALUE : nums2[j - 1];
int nums2RightMin = (j == n) ? Integer.MAX_VALUE : nums2[j];
//判断中位数
if ((m + n) % 2 == 1) {
return Math.max(nums1LeftMax, nums2LeftMax);
} else {
return (Math.max(nums1LeftMax, nums2LeftMax) + Math.min(nums1RightMin, nums2RightMin)) / 2.0;
}
}
栈——有效的括号
题目
思路
经典题目,用栈就好了,遇到左括号就入栈,遇到右括号就出栈。最后栈为空则有效,非空则无效。
代码
public boolean isValid(String s) {
if (s == null || s.length() == 0) {
return true;
}
Stack<Character> stack = new Stack<>();
char[] array = s.toCharArray();
for (char c : array) {
if (c == '(' || c == '[' || c == '{') {
stack.push(c);
} else if (c == ')' || c == ']' || c == '}') {
if (stack.isEmpty()) {
return false;
}
char top = stack.pop();
if (c == ')' && top != '(')
return false;
if (c == ']' && top != '[')
return false;
if (c == '}' && top != '{')
return false;
}
}
return stack.isEmpty();
}
栈——字符串解码
题目
思路
使用 栈(Stack) 来解决这个问题
-
使用两个栈:
- 一个栈保存数字,用来记录重复次数。
- 另一个栈保存当前的字符串,用来记录已经解析的部分。
-
遍历字符串:
- 当遇到数字时,解析出完整的数字。
- 当遇到
[
时,表示要开始处理一个新的编码部分,当前字符串应该入栈等待。 - 当遇到
]
时,表示结束了一个编码部分,取出栈顶的数字和字符串,重复并连接当前的部分。 - 当遇到普通字符时,直接将字符追加到当前的字符串中。
-
时间复杂度分析:
- 每个字符都会被遍历一次,且每个字符在栈中进出一次,因此时间复杂度为 O(n),其中 n 是字符串的长度。
代码
public String decodeString(String s) {
if(s.isEmpty()){
return s;
}
Stack<Integer> integerStack = new Stack<>();
Stack<StringBuilder> stringStack = new Stack<>();
char[] chars = s.toCharArray();
StringBuilder sb = new StringBuilder();
int k = 0;
for (char aChar : chars) {
if (aChar == '[') {
integerStack.push(k);
stringStack.push(sb);
sb = new StringBuilder();
k = 0;
} else if (aChar == ']') {
int temp = integerStack.pop();
StringBuilder str = stringStack.pop();
for (int j = 0; j < temp; j++) {
str.append(sb);
}
sb = str;
} else if (Character.isDigit(aChar)) {
k = k * 10 + (aChar - '0');
} else {
sb.append(aChar);
}
}
return sb.toString();
}