/*
判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
示例 1:
输入: 121
输出: true
示例 2:
输入: -121
输出: false
解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
示例 3:
输入: 10
输出: false
解释: 从右向左读, 为 01 。因此它不是一个回文数。
*/
public boolean isPalindrome(int x) {
/*
特殊情况:
当 x < 0 时,x 不是回文数。
同样地,如果数字的最后一位是 0,为了使该数字为回文,
则其第一位数字也应该是 0
只有 0 满足这一属性
*/
if (x < 0 || (x % 10 == 0 && x != 0)) return false;
int ans = 0;
while (x > ans) {
ans = ans * 10 + x % 10;
x /= 10;
}
/*
当数字长度为奇数时,我们可以通过 revertedNumber/10 去除处于中位的数字。
例如,当输入为 12321 时,在 while 循环的末尾我们可以得到 x = 12,revertedNumber = 123,
由于处于中位的数字不影响回文(它总是与自己相等),所以我们可以简单地将其去除。
当输入为1221时, 在 while 循环的末尾我们可以得到 x = 12,revertedNumber = 12
*/
return x == ans || x == ans/10;
}
/*
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""。
示例 1:
输入: ["flower","flow","flight"]
输出: "fl"
示例 2:
输入: ["dog","racecar","car"]
输出: ""
解释: 输入不存在公共前缀。
编写一个函数来查找字符串数组中的最长公共前缀。
说明:
所有输入只包含小写字母 a-z 。
*/
public String longestCommonPrefix(String[] strs) {
if (str.length == 0) return “”;
String ans = strs[0];
for (int i = 1; i < strs.length; i++) {
int j = 0;
for (;j < ans.length() && j < strs[i].length(); j++) {
if (ans.charAt(j) != ans.strs[i].charAt(j)) {
break;
}
}
ans = ans.substring(0, j);
if (ans.equals(“”)) return ans;
}
return ans
}
/*
时间复杂度:O(mn),其中 mm 是字符串数组中的字符串的平均长度,nn 是字符串的数量。最坏情况下,字符串数组中的每个字符串的每个字符都会被比较一次。
空间复杂度:O(1)。使用的额外空间复杂度为常数。
*/
/*
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: nums = [1,2,3]
输出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
*/
// 方法一 :递归
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> ans = new ArrayList<>();
if (nums == null) {return ans;}
dfs(ans, nums, new ArrayList<Integer>(), 0);
return ans;
}
private void dfs(List<List<Integer>> ans, int[] nums, List<Integer> list, int index) {
// teminator
if (index == nums.length) {
ans.add(new ArrayList<Integer>(list));
return;
}
// not pick the number at this index,不需要发生改变
dfs(ans, nums, list, index + 1);
list.add(nums[index]);
// pick the number at this index 发生改变了
dfs(ans, nums, list, index + 1);
list.remove(list.size() - 1);
}
方法二:
// 方法二:
public static List<List<Integer>> subsets() {
int[] nums = {1, 2, 3};
List<List<Integer>> res = new ArrayList<>();
backtrack(0, nums, res, new ArrayList<Integer>());
return res;
}
private static void backtrack(int i, int[] nums, List<List<Integer>> res, ArrayList<Integer> tmp) {
System.out.println(“i = “ + i ) ;
res.add(new ArrayList<>(tmp));
System.out.println(“res” + res);
for (int j = i; j < nums.length; j++) {
tmp.add(nums[j]);
System.out.println(“i = “ + i + “ j = “ + j) ;
System.out.println(“temp” + tmp);
backtrack(j + 1, nums, res, tmp);
tmp.remove(tmp.size() - 1);
System.out.println(“i = “ + i + “ j = “ + j + “ 移除后的 temp” + tmp);
}
}
/*
打印:
i = 0
res[[]]
i = 0 j = 0
temp[1]
i = 1
res[[], [1]]
i = 1 j = 1
temp[1, 2]
i = 2
res[[], [1], [1, 2]]
i = 2 j = 2
temp[1, 2, 3]
i = 3
res[[], [1], [1, 2], [1, 2, 3]]
i = 2 j = 2 移除后的 temp[1, 2]
i = 1 j = 1 移除后的 temp[1]
i = 1 j = 2
temp[1, 3]
i = 3
res[[], [1], [1, 2], [1, 2, 3], [1, 3]]
i = 1 j = 2 移除后的 temp[1]
i = 0 j = 0 移除后的 temp[]
i = 0 j = 1
temp[2]
i = 2
res[[], [1], [1, 2], [1, 2, 3], [1, 3], [2]]
i = 2 j = 2
temp[2, 3]
i = 3
res[[], [1], [1, 2], [1, 2, 3], [1, 3], [2], [2, 3]]
i = 2 j = 2 移除后的 temp[2]
i = 0 j = 1 移除后的 temp[]
i = 0 j = 2
temp[3]
i = 3
res[[], [1], [1, 2], [1, 2, 3], [1, 3], [2], [2, 3], [3]]
i = 0 j = 2 移除后的 temp[]
*/
方法三:
// 方法三
public List<List<Integer>> subsets(int[] nums) {
// int[] nums = {1, 2 , 3};
List<List<Integer>> output = new ArrayList();
output.add(new ArrayList<Integer>());
for (int num : nums) {
List<List<Integer>> newSubsets = new ArrayList();
System.out.println(“before output “);
// [] -> [1]
//[] [1] -> [2] [1,2]
// [] [1] [2] [1,2]-> [3] [1,3] [2,3] [1,2,3]
for (List<Integer> curr : output) {
newSubsets.add(new ArrayList<Integer>(curr){{add(num);}});
}
System.out.println(“newSubsets”);
for (List<Integer> curr : newSubsets) {
output.add(curr);
}
}
System.out.println(output);
return output;
}
/*
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
*/
// 方法一:递归
public ListNode reverseList(ListNode head) {
if (null == head || head.next == null) return head;
ListNode node = reverseList(head.next);
head.next.next = head;
head.next = null;
return node;
}
// 方法二:迭代
public ListNode reverseList(ListNode head) {
if (null == head) return head;
ListNode pre = null;
while (head != null) {
ListNode temp = head.next;
head.next = pre;
pre = head;
head = temp;
}
return pre;
}
/*
给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
示例:给你这个链表:1->2->3->4->5
当 k = 2 时,应当返回: 2->1->4->3->5
当 k = 3 时,应当返回: 3->2->1->4->5
说明:
你的算法只能使用常数的额外空间。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
*/
public ListNode reverseKGroup(ListNode head, int k) {
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode pre = dummy;
ListNode end = dummy;
while (end.next != null) {
for (int i = 0; i < k && end != null; i++) {
end = end.next;
}
if (end == null) break;
ListNode next = end.next;
ListNode start = pre.next;
end.next = null;
pre.next = reverse(start);
start.next = next;
pre = start;
end = pre;
}
return dummy.next;
}
public ListNode reverse(ListNode head) {
ListNode pre = null;
ListNode curr = head;
while (curr != null) {
ListNode temp = curr.next;
curr.next = pre;
pre = curr;
curr = temp;
}
return pre;
}
/*
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例:给定 1->2->3->4, 你应该返回 2->1->4->3.
*/
// 方法一
public ListNode swapPairs(ListNode head) {
if (null == head || head.next == null) return head;
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode pre = dummy;
// -1 1 2 3 4
// -1 2 1 3 4
while (head != null && head.next != null) {
ListNode firstNode = head;
ListNode secondNode = head.next;
pre.next = secondNode;
firstNode.next = secondNode.next;
secondNode.next = firstNode;
pre = head;
head = head.next;
}
}
// 方法二
public ListNode swapPairs(ListNode head) {
if (null == head || head.next == null) return head;
ListNode firstNode = head;
ListNode secondNode = head.next;
// 1 ,2 3 4
// 1 2 node 4 3
firstNode.next = swapPairs(secondNode.next);
secondNode.next = firstNode;
return secondNode;
}
/*
给定一组字符,使用原地算法将其压缩。压缩后的长度必须始终小于或等于原数组长度。
数组的每个元素应该是长度为1 的字符(不是 int 整数类型)。
在完成原地修改输入数组后,返回数组的新长度。
进阶:你能否仅使用O(1) 空间解决问题?
示例 1:
输入:["a","a","b","b","c","c","c"]
输出:返回 6 ,输入数组的前 6 个字符应该是:["a","2","b","2","c","3"]
说明:
"aa" 被 "a2" 替代。"bb" 被 "b2" 替代。"ccc" 被 "c3" 替代。
示例 2:
输入:["a"]
输出:返回 1 ,输入数组的前 1 个字符应该是:["a"]
解释:没有任何字符串被替代。
示例 3:
输入:["a","b","b","b","b","b","b","b","b","b","b","b","b"]
输出:返回 4 ,输入数组的前4个字符应该是:["a","b","1","2"]。
解释:
由于字符 "a" 不重复,所以不会被压缩。"bbbbbbbbbbbb" 被 “b12” 替代。
注意每个数字在数组中都有它自己的位置。
*/
public int compress(char[] chars) {
if (chars.length < 2) return 0;
int j = 0;
int count = 1;
for (int i = 1; i < chars.length; i++) {
if (chars[i] == chars[j]) {
count++;
}else {
j++;
chars[j] = to_string(count);
j = i;
count = 1;
}
}
j++;
chars[j] = to_string(count);
return j + 1;
}
/*
字符串压缩。利用字符重复出现的次数,编写一种方法,实现基本的字符串压缩功能。比如,字符串aabcccccaaa会变为a2b1c5a3。若“压缩”后的字符串没有变短,则返回原先的字符串。你可以假设字符串中只包含大小写英文字母(a至z)。
示例1: 输入:"aabcccccaaa"
输出:"a2b1c5a3"
示例2: 输入:"abbccd"
输出:"abbccd"
解释:"abbccd"压缩后为"a1b2c2d1",比原字符串长度更长。
*/
public String compressString(String S) {
if (S == null || S.length() < 2) return S;
StringBuilder sb = new StringBuilder();
int count = 1;
int j = 0;
for (int i = 1; i < S.length(); i++) {
if (S.charAt(i) == S.charAt(j)) {
count++;
}else {
sb.append(S.charAt(j)).append(count);
j = i;
count = 1;
}
}
sb.append(S.charAt(j)).append(count);
return sb.length() < S.length() ? sb.toString() : S;
}
/*
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。
示例 1:输入:["h","e","l","l","o"]
输出:["o","l","l","e","h"]
示例 2:输入:["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]
*/
// 方法一:
public void reverseString(char[] s) {
int left = 0;
int length = s.length;
while (left < length / 2) {
char temp = s[left];
s[left] = s[length - 1 - left];
s[length - 1 - left] = temp;
left++;
}
}
/*
// h e l l o
// 0 , 5
// o e l l h
// o l l e h
*/
//或者
public void reverseString(char[] s) {
int left = 0;
int right = s.length - 1;
while (left <= right) {
this.swap(s, left, right);
left++;
right--;
}
}
public void swap(char[] s, int i, int j) {
if (i == j) return;
s[i] ^= s[j];
s[j] ^= s[i];
s[i] ^= s[j];
}
// 方法二:递归
public void reverseString(char[] s) {
reverseHelper(s, 0, s.length - 1);
}
public void reverseHelper(char[] s, int left, int right) {
if (left >= right) return;
char temp = s[left];
s[left++] = s[right];
s[right--] = temp;
reverseHelper(s, left, right);
}
// 方法二:双指针
public void reverseString(char[] s) {
int left = 0, right = s.length - 1;
while (left < right) {
char temp = s[left];
s[left++] = s[right];
s[right--] = temp;
}
}
/*
给定一个字符串 s 和一个整数 k,你需要对从字符串开头算起的每隔 2k 个字符的前 k 个字符进行反转。
如果剩余字符少于 k 个,则将剩余字符全部反转。
如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。
示例:输入: s = "abcdefg", k = 2
输出: "bacdfeg"
*/
// k个反转
public String reverseStr(String s, int k) {
char[] strArray = s.toCharArray();
for (int start = 0; start < strArray.length; start+= 2 * k) {
int left = start;
int right = Math.min(start + k - 1, strArray.length - 1);
while (left < right) {
char temp = strArray[left];
strArray[left++] = strArray[right];
strArray[right--] = temp;
}
}
return new String(strArray);
}
/*
给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。
示例 1:输入: 123 输出: 321
示例 2:输入: -123 输出: -321
示例 3:输入: 120 输出: 21
注意:
假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−231, 231 − 1]。请根据这个假设,如果反转后整数溢出那么就返回 0。
*/
public int reverse(int x) {
int base = 0;
while (x != 0) {
int value = x % 10;
if (base > Integer.MAX_VALUE / 10 || (base == Integer.MAX_VALUE / 10 && value > 7)) {
return 0;
}
if (base < Integer.MIN_VALUE / 10 || (base == Integer.MIN_VALUE / 10 && value < -8)) {
return 0;
}
base = base * 10 + value;
x /= 10;
}
return base;
}
/*
请你来实现一个 atoi 函数,使其能将字符串转换成整数。
首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。接下来的转化规则如下:
如果第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字字符组合起来,形成一个有符号整数。
假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成一个整数。
该字符串在有效的整数部分之后也可能会存在多余的字符,那么这些字符可以被忽略,它们对函数不应该造成影响。
注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换,即无法进行有效转换。
在任何情况下,若函数不能进行有效的转换时,请返回 0 。
提示:
本题中的空白字符只包括空格字符 ' ' 。
假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231, 231 − 1]。如果数值超过这个范围,请返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。
示例 1:输入: "42" 输出: 42
示例 2:输入: " -42" 输出: -42
解释: 第一个非空白字符为 '-', 它是一个负号。
我们尽可能将负号与后面所有连续出现的数字组合起来,最后得到 -42 。
示例 3:输入: "4193 with words" 输出: 4193
解释: 转换截止于数字 '3' ,因为它的下一个字符不为数字。
示例 4:输入: "words and 987" 输出: 0
解释: 第一个非空字符是 'w', 但它不是数字或正、负号。
因此无法执行有效的转换。
示例 5:输入: "-91283472332" 输出: -2147483648
解释: 数字 "-91283472332" 超过 32 位有符号整数范围。
因此返回 INT_MIN (−231) 。
*/
public int myAtoi(String str) {
//有效验证
if (str.isEmpty()) return 0;
// 正负号
int sign = 1;
// 转换值
int base = 0;
// 索引
int i = 0;
// 空格判断
while (i < str.length() && str.charAt(i) == ' ') {
i++;
}
// sign赋值
if (i < str.length() && (str.charAt(i) == '-' || str.charAt(i) == '+')) {
sign = str.charAt(i++) == '-' ? -1 : 1;
}
while (i < str.length() && str.charAt(i) >= '0' && str.charAt(i) <= '9') {
int value = str.charAt(i++) - '0';
if (base > Integer.MAX_VALUE / 10 || (base == Integer.MAX_VALUE / 10 && value > 7)) {
return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
}
base = base * 10 + value;
}
return sign * base;
}
/*
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
示例:给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
*/
// 方法一 暴力O(n^2),空间O(1)
public int[] twoSum(int[] nums, int target) {
int[] resultArray = new int[2];
for (int i = 0; i < nums.length - 1; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] == target) {
resultArray[0] = i;
resultArray[1] = j;
return resultArray;
}
}
}
return resultArray;
}
// 方法二:时间O(n), 空间O(n)
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
int[] result = new int[2];
for (int i = 0; i < nums.length; i++) {
int comp = target - nums[i];
if (map.containsKey(comp)) {
result[0] = map.get(comp);
result[1] = i;
return result;
}
map.put(nums[i], i);
}
return result;
}
// 或者
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (map.containsKey(complement)) {
return new int[] { map.get(complement), i };
}
map.put(nums[i], i);
}
throw new IllegalArgumentException(“No two sum solution”);
}
/*
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例:给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
*/
// 暴力
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
if (null == nums || nums.length < 3) {return result;}
Arrays.sort(nums);
if (nums[0] > 0 || nums[nums.length - 1] < 0 )return result;
for (int i = 0; i < nums.length - 2; i++) {
if (i > 0 && nums[i] == nums[i - 1])continue;
for (int j = i + 1; j < nums.length - 1; j++) {
if (j > i + 1 && nums[j] == nums[j - 1]) continue;
for (int k = j + 1; k < nums.length; k++) {
if (k > j + 1 && nums[k] == nums[k - 1]) continue;
if (nums[i] + nums[j] + nums[k] == 0) {
List<Integer> list = new ArrayList<>();
list.add(nums[i]);
list.add(nums[j]);
list.add(nums[k]);
result.add(list);
}
}
}
}
return result;
}
// 方法二: 双指针
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<List<Integer>>();
if (null == nums || nums.length < 2) return result;
Arrays.sort(nums);
if (nums[0] > 0 || nums[nums.length - 1] < 0) return result;
for (int i = 0; i < nums.length; i++) {
if (nums[i] > 0) break;
if (i > 0 && nums[i] == nums[i - 1])continue;
int j = i + 1;
int k = nums.length - 1;
while (j < k) {
int sum = nums[i] + nums[j] + nums[k];
if (sum == 0) {
List<Integer> list = new ArrayList<Integer>();
list.add(nums[i]);
list.add(nums[j]);
list.add(nums[k]);
result.add(list);
while (j < k && nums[j] == nums[++j]);
while (j < k && nums[k] == nums[--k]);
}else if (sum > 0) {
while (j < k && nums[k] == nums[--k]);
}else if (sum < 0) {
while (j < k && nums[j] == nums[++j]);
}
}
}
return result;
}
/*
给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
*/
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(0), p = l1, q = l2;
ListNode pre = dummy;
int carry = 0;
while (p != null || q != null) {
int x = (p != null) ? p.val : 0;
int y = (q != null) ? q.val : 0;
int sum = x + y + carry;
carry = sum / 10;
pre.next = new ListNode(sum % 10);
pre = pre.next;
if (p != null) p = p.next;
if (q != null) q = q.next;
}
if (carry > 0) {
pre.next = new ListNode(carry);
}
return dummy.next;
}
/*
给定一个二叉树,判断其是否是一个有效的二叉搜索树。假设一个二叉搜索树具有如下特征:
节点的左子树只包含小于当前节点的数。
节点的右子树只包含大于当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入:
2
/ \
1 3
输出: true
示例 2:
输入:
5
/ \
1 4
/ \
3 6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。
根节点的值为 5 ,但是其右子节点值为 4 。
*/
// 方法一: 递归 时间O(n) 空间递归用栈空间O(n)
public boolean isValidBST(TreeNode root) {
return recurse(root, null, null);
}
public boolean recurse(TreeNode node, Integer lower, Integer upper) {
if (node == null) {
return true;
}
int value = node.val;
if (lower != null && val <= lower) return false;
if (upper != null && val >= upper) return false;
if (!recurse(node.right, val, upper)) return false;
if (!recurse(node.left, lower, val)) return false;
return true;
}
// 方法二: 中序遍历 栈 时间O(n) , 空间O(n) 开辟的stack
public boolean isValidBST(TreeNode root) {
Stack<TreeNode> stack = new Stack();
Integer inorder = null;
while (!stack.isEmpty() || root != null) {
while (root != null) {
stack.push(root);
root = root.left;
}
root = stack.pop();
if (inorder != null && root.val <= inorder) return false;
inorder = root.val;
root = root.right;
}
return true;
}
/*
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例:给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回它的最大深度 3 。
*/
// 方法一:递归: 时间复杂度为 O(N)O(N)
public int maxDepth(TreeNode root) {
if (null == root) return 0;
int maxDepth = Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
return maxDepth;
}
// 方法二:迭代 时间O(n),空间O(n)
public int maxDepth(TreeNode root) {
Queue<Pair<TreeNode, Integer>> stack = new LinkedList<>();
if (root != null) {
stack.add(new Pair(root, 1));
}
int depth = 0;
while (!stack.isEmpty()) {
Pair<TreeNode, Integer> current = stack.poll();
TreeNode node = current.getKey();
int currentDepth = current.getValue();
if (node != null) {
depth = Math.max(depth, currentDepth);
stack.add(new Pair(node.left, currentDepth + 1));
stack.add(new Pair(node.right, currentDepth + 1));
}
}
return depth;
}
/*
给定一个二叉树,返回它的 前序 遍历。
示例:输入: [1,null,2,3]
1
\
2
/
3
输出: [1,2,3]
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
*/
// 方法一: 递归 时间O(n),每个节点都遍历一遍, 空间O(n) 节点个数
List<Integer> list = new ArrayList<>();
public List<Integer> preorderTraversal(TreeNode root) {
if (null == root) return list;
list.add(root.val);
if (null != root.left) {
preorderTraversal(root.left);
}
if (null != root.right) {
preorderTraversal(root.right);
}
// 方法二: 栈实现 迭代 时O(n),空间O(n)这三个方法的区别是有的用LinkedList做为栈添加到栈用add,移除栈顶 用pollLast(),而有的用Stack作为栈,添加用.push(),移除栈顶用.pop, 而输出函数可以用LinkedList,也可以用ArrayList,都有add方法,注意点先将右压入栈,再将左压入栈,那么先出来左再出来右
// 1
public List<Integer> preorderTraversal(TreeNode root) {
LinkedList<TreeNode> stack = new LinkedList<>();
LinkedList<Integer> output = new LinkedList<>();
if (null == root) return output;
stack.add(root);
while(!stack.isEmpty()) {
TreeNode node = stack.pollLast();
output.add(node.val);
if (null != node.right) {
stack.add(node.right);
}
if (null != node.left) {
stack.add(node.left);
}
}
return output;
}
// 2
public List<Integer> preorderTraversal(TreeNode root) {
LinkedList<TreeNode> stack = new LinkedList<>();
ArrayList<Integer> output = new ArrayList<>();
if (null == root) return output;
stack.add(root);
while(!stack.isEmpty()) {
TreeNode node = stack.pollLast();
output.add(node.val);
if (null != node.right) {
stack.add(node.right);
}
if (null != node.left) {
stack.add(node.left);
}
}
return output;
}
// 3
public List<Integer> preorderTraversal(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
List<Integer> output = new ArrayList<>();
if (null == root) return output;
stack.push(root);
while(!stack.isEmpty()) {
TreeNode node = stack.pop();
output.add(node.val);
if (null != node.right) {
stack.push(node.right);
}
if (null != node.left) {
stack.push(node.left);
}
}
return output;
}
/*
给定一个 N 叉树,返回其节点值的前序遍历。
例如,给定一个 3叉树 :
返回其前序遍历: [1,3,5,6,2,4]。
说明: 递归法很简单,你可以使用迭代法完成此题吗?
*/
// 递归: 时间复杂度O(M),M为N叉树中的子节点个数,空间复杂度为O(M),M表示节点个数
// 1
List<Integer> list = new ArrayList<>();
public List<Integer> preorder(Node root) {
if (null == root) return list;
list.add(root.val);
/*
for (int i = 0; i < root.children.size(); i++) {
Node item = root.children.get(i);
preorder(item);
}
体会这两个for的不同
*/
for (Node node: root.children) {
preorder(node);
}
return list;
}
// 2
public List<Integer> preorder(Node root) {
List<Integer> list = new ArrayList<>();
if (root == null) return list;
begin_preorder(root, list);
return list;
}
public void begin_preorder(Node node, List<Integer> list) {
if (node != null) {
list.add(node.val);
for (Node nodeF: node.children) {
begin_preorder(nodeF, list);
}
}
}
// 方法二迭代 : 时间复杂度O(M),M为N叉树中的节点数,每个节点入栈出栈各一次,空间复杂度为O(M),M表示栈的大小
public List<Integer> preorder(Node root) {
List<Integer> list = new ArrayList<>();
Stack<Node> stack = new Stack<>();
if (null == root) return list;
stack.push(root);
while (!stack.isEmpty()) {
Node item = stack.pop();
list.add(item.val);
Collections.reverse(node.children);
for (Node item: node.children) {
stack.add(item);
}
/* 体会上面和下面的不同
for (int i = item.children.size() - 1; i >= 0; i--) {
stack.push(item.children.get(i));
}
*/
}
return list;
}
/*
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
*/
// 方法一 O(M+N), O(1)
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode prehead = new ListNode(-1);
ListNode prev = prehead;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
prev.next = l1;
l1 = l1.next;
} else {
prev.next = l2;
l2 = l2.next;
}
prev = prev.next;
}
// 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
prev.next = l1 == null ? l2 : l1;
return prehead.next;
}
// 方法二 递归 时间O(M+N) 空间O(M+N)递归深度
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null) {
return l2;
}
else if (l2 == null) {
return l1;
}
else if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
}
else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
}
/*
给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
说明:
初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。
你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
示例:
输入:nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3
输出: [1,2,2,3,5,6]
*/
/*须知
int nums1 = [1, 2, 3, 0, 0, 0], m = 3;
int nums2 = [2, 5, 6], n = 3;
System.arraycopy(src, srcPos, dest, destPos, length)
System.arraycopy(nums2, 0, nums1, m, n)
nums1 = [1, 2, 3, 2, 5, 6];
*/
// 方法一暴力 时间复杂度O((n+m)log(n+m)) 空间复杂度O(1)
public void merge(int[] nums1, int m, int[] nums2, int n) {
System.arraycopy(nums2, 0, nums1, m, n);
Arrays.sort(nums1);
}
// 方法二 双指针
public void merge(int[] nums1, int m, int[] nums2, int n) {
int p1 = m - 1;
int p2 = n - 1;
int p = m + n - 1;
while (p1 >= 0 && p2 >= 0) {
nums1[p—-] = nums1[p1] < nums2[p2] ? nums2[p2—-] : nums1[p1—-];
}
System.arraycopy(nums2, 0, nums1, 0, p2 + 1);
}
/*
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
示例:输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
说明:必须在原数组上操作,不能拷贝额外的数组。尽量减少操作次数。
*/
// ******方法一:O(n), O(1)
public void moveZeroes(int[] nums) {
if(null == nums || nums.length <= 0) return 0;
int i = 0;
for (int j = 0; j < nums.length; j++) {
if (nums[j] != 0) {
if (i != j) {
nums[i] = nums[j];
nums[j] = 0 ;
}
i++;
}
}
// 方法二
public void moveZeroes(int[] nums) {
if(null == nums || nums.length <= 0) return 0; int i = 0;
for (int j = 0; j < nums.length; j++) {
if (nums[j] != 0) {
nums[i++] = nums[j];
}
}
for (int j = i; j < nums.length; j++) {
nums[j] = 0;
}
}
// 方法三*********很重要方法二,交换(覆盖)
public void moveZeroes(int[] nums) {
int i = 0;
for (int j = 0; j < nums.length; j++) {
if (nums[j] != 0) {
this.swap(nums, i++, j);
}
}
}
public void swap(int[] nums, int i, int j) {
if (i == j)return;
nums[i] = nums[i] ^ nums[j];
nums[j] = nums[i] ^ nums[j];
nums[i] = nums[i] ^ nums[j];
}
// 方法四 *****通过count值计算前面的思路:统计0的个数,如果count 大于0 ,就将非零元素移动到当前位置减去0元素个数的位置上,将当前位置用0填充
public void moveZeroes(int[] nums) {
int count = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] == 0) {
count++;
}else if (count > 0) {
nums[i - count] = nums[i];
nums[i] = 0;
}
}
}
/*
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1:给定 nums = [3,2,2,3], val = 3,
函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:给定 nums = [0,1,2,2,3,0,4,2], val = 2,
函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
注意这五个元素可为任意顺序。
你不需要考虑数组中超出新长度后面的元素。
说明:为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝
int len = removeElement(nums, val);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
*/
// 方法一: 暴力 时O(n^2),空O(1)
public int removeElement(int[] nums, int val) {
if(null == nums || nums.length <= 0) return 0; int length = nums.length;
for (int i = 0; i < length; i++) {
if (nums[i] == val) {
this.removeIndex(nums, i);
i--;
length--;
}
}
return length;
}
public void removeIndex(int[] nums, int index) {
for (int i = index + 1; i < nums.length; i++) {
nums[i - 1] = nums[i];
}
}
// 方法二: 双指针时O(n),空O(1)
// 1
public int removeElement(int[] nums, int val) {
int j = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] != val) {
nums[j++] = nums[i];
}
}
return j;
}
// 2
public int removeElement(int[] nums, int val) {
int p = 0;
int q = 0;
while(q < nums.length) {
if (nums[q] != val) {
nums[p++] = nums[q];
}
q++;
}
return p;
}
// 方法三: 覆盖 双指针O(n),空O(1)
public int removeElement(int[] nums, int val) {
int p = 0;
int q = nums.length;
while(p < q) {
if (nums[p] == val) {
nums[p] = nums[q - 1];
q--;
}else {
p++;
}
}
return p;
}
/*
给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
示例 1: 给定数组 nums = [1,1,2],
函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:给定 nums = [0,0,1,1,1,2,2,3,3,4],
函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。
你不需要考虑数组中超出新长度后面的元素
*/
public int removeDuplicates(int[] nums) {
int j = 1;
for (int i = 1; i < nums.length; i++) {
if (nums[i - 1] != nums[i]) {
nums[j++] = nums[i];
}
}
return j;
}
/*
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
示例 1:给定 nums = [1,1,1,2,2,3],
函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3 。
你不需要考虑数组中超出新长度后面的元素。
示例 2:给定 nums = [0,0,1,1,1,1,2,3,3],
函数应返回新长度 length = 7, 并且原数组的前五个元素被修改为 0, 0, 1, 1, 2, 3, 3 。
你不需要考虑数组中超出新长度后面的元素。
*/
public int removeDuplicates(int[] nums) {
if (null == nums || nums.length < 1) return 0;
int length = nums.length;
int count = 1;
for (int i = 1; i < length; i++) {
if (nums[i - 1] == nums[i]) {
count++;
if (count > 2) {
this.removeIndex(nums, i);
i--;
length--;
}
}else {
count = 1;
}
}
return length;
}
public void removeIndex(int[] nums, int index) {
for (int i = index + 1; i < nums.length; i++) {
nums[i - 1] = nums[i];
}
}
/*
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:输入: 2 输出: 2
解释: 有两种方法可以爬到楼顶。
27. 1 阶 + 1 阶
28. 2 阶
示例 2:输入: 3 输出: 3
解释: 有三种方法可以爬到楼顶。
29. 1 阶 + 1 阶 + 1 阶
30. 1 阶 + 2 阶
31. 2 阶 + 1 阶
*/
//方法一:暴力法 (时 O(2^n),空O(n)深度达n层)
public int climbStairs(int n) {
return climb_Stairs(0, n);//从第0阶开始,到第n阶有多少种方法
}
public int climb_Stairs(int i, int n) {
if (i > n) return 0; // 上一次的第i个台阶走了1或者2步后超过了第n个台阶,没有到达目标,方法数为0
if (i == n) return 1; // 上一次的第i个台阶走了1或者2步到达了第n个台阶,表示有一种方法
return climb_Stairs(i + 1, n) + climb_Stairs(i + 2, n);
}
// 方法二:记忆递归(时O(n),空O(n)深度达n层+开辟n个空间)
public int climbStairs(int n) {
int[] mems = new int[n + 1];
return climb_Stairs(0, n, mems);
}
public int climb_Stairs(int i, int n, int[] mems) {
if (i > n) return 0;
if (i == n) return 1;
if (mems[i] > 0) return mems[i];
mems[i] = climb_Stairs(i + 1, n, mems) + climb_Stairs(i + 2, n, mems);
return mems[i];
}
// 方法三:动态规划 (时O(n),空O(n))
// 1
public int climbStairs(int n) {
if (n == 1) return 1;
int[] dp = new int[n + 1];
dp[1] = 1;
dp[2] = 2;
for (int i = 3; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
// 2
public int climbStairs(int n) {
if (n == 1) return 1;
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];
}
// 3
public int climbStairs(int n) {
int[] dp = new int[n + 2];// 如果不为n + 2,当n = 1时执行到dp[2]越界
dp[1] = 1;
dp[2] = 2;
for (int i = 3; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
// 4
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];
}
// 方法四:斐波那契时O(n),空O(1)
public int climbStairs(int n) {
if (n == 1) return 1;
int first = 1;
int second = 2;
for (int i = 3; i <= n; i++) {
second = second + first;
first = second - first;
}
return second;
}
/*
斐波那契数,通常用 F(n) 表示,形成的序列称为斐波那契数列。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
给定 N,计算 F(N)。
示例 1:输入:2 输出:1
解释:F(2) = F(1) + F(0) = 1 + 0 = 1.
示例 2:输入:3 输出:2
解释:F(3) = F(2) + F(1) = 1 + 1 = 2.
示例 3:输入:4 输出:3
解释:F(4) = F(3) + F(2) = 2 + 1 = 3.
提示:0 ≤ N ≤ 30
*/
// 方法一:暴力 时间O(2^n),空O(n)深度达n
public int fib(int n) {
if (n < 2) return n;
return fib(n - 1) + fib(n - 2);
}
// 方法二:自底向上时O(n),空O(n)
public int fib(int n) {
if (n < 2) return n;
int[] caches = new int[n + 1];
caches[0] = 0;
caches[1] = 1;
return fib_bottomToUp(caches, n);
}
public int fib_bottomToUp(int[] caches, int n) {
for (int i = 2; i <= n; i++) {
caches[i] = caches[i - 1] + caches[i - 2];
}
return caches[n];
}
// 方法三:自顶向下时O(n),空O(n)
public int fib(int n) {
if (n < 2) return n;
int[] caches = new int[n + 1];
for (int i = 0; i <= caches.length; i++) {
caches[i] = -1;
}
cache[0] = 0;
cache[1] = 1;
return fib_upToDown(caches, n);
}
public int fib_upToDown(int[] caches, int n) {
if (caches[n] != -1) {
return caches[n];
} // 这里总写错注意注意总是写成caches[n] = caches[n - 1] + caches[n - 2];显然前面的都还没有值所以这样要注意
caches[n] = fib_upToDown(caches, n - 1) + fib_upToDown(caches, n - 2);
return caches[n]
}
// 方法四:常数保存基数时O(n),空O(1)
public int fib(int n) {
if (n < 2) return n;
int first = 0;
int second = 1;
for (int i = 2; i <= n; i++) {
second = second + first;
first = second - first;
}
return second;
}
/*
给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明:你不能倾斜容器,且 n 的值至少为 2。
图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
示例:输入:[1,8,6,2,5,4,8,3,7]
输出:49
*/
// 方法一: 暴力 O(n^2),空O(1)
public int maxArea(int[] height) {
int res = 0;
for (int i = 0; i < height.length - 1; i++) {
for (int j = 1; j < height.length; j++) {
res = Math.max(res, (height[i] < height[j]) ? height[i] * (j - i) : height[j] * (j - i));
}
}
return res;
}
// 方法二: 双指针 O(n),空O(1)这里一定要注意这里用双指针的话由于最后一个height.length -1是可以用做比较取值的,所以这里必须是height.length - 1
int res = 0;
int left = 0;
int right = height.length - 1;
while(left < right) {
int minHeight = height[left] < height[right] ? height[left] : height[right];
res = Math.max(res, minHeight * (right - left));
if (minHeight == height[left]) {
left++;
}else {
right--;
}
}
return res;
}
// 2
int j = height.length - 1;
int max = 0;
for (int i = 0; i < j; ) {
int minHeight = height[i] < height[j] ? height[i++] : a[j—];
int area = (j - i + 1) * minHeight;
max = Math.max(max, area);
}
return max;
/*
编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。
示例 1:输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。
示例 2:输入:00000000000000000000000010000000
输出:1
解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。
示例 3:输入:11111111111111111111111111111101
输出:31
解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 '1'
*/
// 方法一
public int hammingWeight(int n) {
int count = 0;
while (num != 0) {
if ((num & 1) == 1) {
count++;
}
num >>= 1;
}
}
// 方法二
public int hammingWeight(int num) {
int sum = 0;
while (num != 0) {
sum++;
num &= (num - 1);
}
return sum;
}
/*
给定一个整数,编写一个算法将这个数转换为十六进制数。对于负整数,我们通常使用 补码运算 方法。
注意:
十六进制中所有字母(a-f)都必须是小写。
十六进制字符串中不能包含多余的前导零。如果要转化的数为0,那么以单个字符'0'来表示;对于其他情况,十六进制字符串中的第一个字符将不会是0字符。
给定的数确保在32位有符号整数范围内。
不能使用任何由库提供的将数字直接转换或格式化为十六进制的方法。
示例 1:输入:26 输出: "1a"
示例 2:输入: -1 输出: "ffffffff"
*/
public static String toHex(int num) {
if(num == 0)return "0";
StringBuffer str = new StringBuffer();
char[] arr = "0123456789abcdef".toCharArray();
if(num != 0) {
int temp = num & 15;
str.append(arr[temp]);
num >>= 4;
}
str.reverse().toString();
return str;
}
/*
实现函数 ToLowerCase(),该函数接收一个字符串参数 str,并将该字符串中的大写字母转换成小写字母,之后返回新的字符串。
示例 1:输入: "Hello" 输出: "hello"
示例 2:输入: "here" 输出: "here"
示例 3:输入: "LOVELY" 输出: "lovely"
‘A’ - ‘Z’ 对应的 ascii 是 65 - 90;
‘a’ - ‘z’ 对应的 ascii 是 97 - 122
*/
// 方法一
public String toLowerCase(String str) {
StringBuilder result = new StringBuilder();
for(int i = 0; i < str.length(); i++) {
if(str.charAt(i) < 65 || str.charAt(i) > 90) {
result.append(str.charAt(i));
}else{
result.append((char) ((int) (str.charAt(i)) + 32));
}
}
return result.toString();
}
大写字母ASCLL码:65-90
小写字母ASCLL码:97-122
注意过滤掉除了字母以外的字符,十七正常输出
// 方法二
public: string toLowerCase(string str) {
/* 正常思路
int change_num='a'-'A';
string res="";
for(auto s:str)
{
if(s>='A'&&s<='Z') res+=(s+change_num);
else res+=s;
}
return res;
*/
/* 位运算(解题区的思路
大写变小写、小写变大写 : 字符 ^= 32;
大写变小写、小写变小写 : 字符 |= 32;
小写变大写、大写变大写 : 字符 &= -33;
eg:
65(A)->二进制表示为100 0001
32的二进制表示为 010 0000
100 0001|010 0000=110 0001->97(a)
eg1:
97(a)->二进制表示 110 0001
33的二进制表示为 010 0001
-33的二进制表示为101 1110 + 1 = 101 1111
110 0001
&101 1111
100 0001 -> 65(A)
*/
for(auto& s:str)
s|=32;
return str;
}
/*
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例 1:输入: nums = [-1,0,3,5,9,12], target = 9 输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例 2:输入: nums = [-1,0,3,5,9,12], target = 2 输出: -1
解释: 2 不存在 nums 中因此返回 -1
*/
public int search(int[] nums, int target) {
int left = 0, right = nums.length - 1, mid;
// 注意这里可以取到等于
/*
输入 [5] 5 输出 -1 预期结果 0, 所以这里必须取到=
*/
while (left <= right) {
mid = left + (right - left) / 2;
if (nums[mid] == target) return mid;
if (nums[mid] > target) {
right = mid - 1;
}else {
left = mid + 1;
}
}
return -1;
}
/*
给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
注意:不能使用代码库中的排序函数来解决这道题。
示例:输入: [2,0,2,1,1,0] 输出: [0,0,1,1,2,2]
进阶:
一个直观的解决方案是使用计数排序的两趟扫描算法。
首先,迭代计算出0、1 和 2 元素的个数,然后按照0、1、2的排序,重写当前数组。
你能想出一个仅使用常数空间的一趟扫描算法吗?
*/
// 方法一
public void sortColors(int[] nums) {
int p0 = 0, p2 = nums.length - 1, curr = 0;
while (curr <= p2) {
if (nums[curr] == 0) {
swap(nums, p0, curr);
p0++;
curr++;
} else if (nums[curr] == 2) {
swap(nums, p2, curr);
p2--;
} else {
curr++;
}
}
}
public void swap(int[] nums, int i, int j) {
if (i == j) return;
nums[i] ^= nums[j];
nums[j] ^= nums[i];
nums[i] ^= nums[j];
}
// 上下两种方法意思相同
public void sortColors(int[] nums) {
int p0 = 0, p2 = nums.length, curr = 0;
while (curr < p2) {
if (nums[curr] == 0) {
swap(nums, p0, curr);
p0++;
curr++;
} else if (nums[curr] == 2) {
p2--;
swap(nums, p2, curr);
} else {
curr++;
}
}
}
public void swap(int[] nums, int i, int j) {
if (i == j) return;
nums[i] ^= nums[j];
nums[j] ^= nums[i];
nums[i] ^= nums[j];
}
/*
给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。
示例 1: 输入: [1,2,3,4,5,6,7] 和 k = 3 输出: [5,6,7,1,2,3,4]
解释:
向右旋转 1 步: [7,1,2,3,4,5,6]
向右旋转 2 步: [6,7,1,2,3,4,5]
向右旋转 3 步: [5,6,7,1,2,3,4]
示例 2:输入: [-1,-100,3,99] 和 k = 2 输出: [3,99,-1,-100]
解释:
向右旋转 1 步: [99,-1,-100,3]
向右旋转 2 步: [3,99,-1,-100]
*/
// 方法一 暴力最简单的方法是旋转 k 次,每次将数组旋转 1 个元素。时间复杂度:O(n*k) 。每个元素都被移动 1 步O(n) k次O(k) 。空间复杂度:O(1) 。没有额外空间被使用。
public void rotate(int[] nums, int k) {
int temp, previous;
for (int j = 0; j < k; j++) {
previous = nums[nums.length - 1];
for (int i = 0; i < nums.length; i ++) {
temp = nums[i];
nums[i] = previous;
previous = temp;
}
}
}
// 方法二: 使用额外的数组算法我们可以用一个额外的数组来将每个元素放到正确的位置上,也就是原本数组里下标为 ii 的我们把它放到 (i+k)\%数组长度(i+k)%数组长度 的位置。然后把新的数组拷贝到原数组中。复杂度分析 时间复杂度: O(n) 。将数字放到新的数组中需要一遍遍历,另一边来把新数组的元素拷贝回原数组。空间复杂度: O(n))。另一个数组需要原数组长度的空间。
public void rotate(int[] nums, int k) {
int[] a = new int[nums.length];
for (int i = 0; i < nums.length; i++) {
a[(i + k) % nums.length] = nums[i];
}
for (int i = 0; i < nums.length; i++) {
nums[i] = a[i];
}
}
//方法三: 使用反转 算法 这个方法基于这个事实:当我们旋转数组 k 次, k\%n 个尾部元素会被移动到头部,剩下的元素会被向后移动。在这个方法中,我们首先将所有元素反转。然后反转前 k 个元素,再反转后面 n-k 个元素,就能得到想要的结果。假设 n=7 且 k=3 。复杂度分析时间复杂度:O(n)。 n 个元素被反转了总共 3 次。空间复杂度:O(1) 。 没有使用额外的空间。
/*
原始数组 : 1 2 3 4 5 6 7
反转所有数字后 : 7 6 5 4 3 2 1
反转前 k 个数字后 : 5 6 7 4 3 2 1
反转后 n-k 个数字后 : 5 6 7 1 2 3 4 --> 结果
*/
public void rotate(int[] nums, int k) {
k %= nums.length;
reverse(nums, 0, nums.length - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, nums.length - 1);
}
public void reverse(int[] nums, int start, int end) {
while (start < end) {
int temp = nums[start];
nums[start] = nums[end];
nums[end] = temp;
start++;
end--;
}
}
/*
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
请找出其中最小的元素。
你可以假设数组中不存在重复元素。
示例 1:输入: [3,4,5,1,2] 输出: 1
示例 2:输入: [4,5,6,7,0,1,2] 输出: 0
*/
//方法一:暴力, 时间复杂度为O(n)
public int findMin(int[] nums) {
int min = nums[nums.length - 1], temp, pre = nums[nums.length - 1];
for (int i = 0; i < nums.length; i++) {
min = min < pre ? min : pre;
temp = nums[i];
nums[i] = pre;
pre = temp;
}
return min;
}
// 方法一:二分搜索 O(logN)
/*
2 3 4 5 6 7
4 5 6 7 2 3 变化点:所有变化点的左侧元素都大于数组第一个元素,变化点右侧元素都小于数组第一个元素
7 2 3 4 5 6
找数组中间元素mid
如果中间元素 > 数组第一个元素, 我们需要在 mid 右边搜索变化点。
如果中间元素 < 数组第一个元素,我们需要在 mid 左边搜索变化点
当我们找到变化点时停止搜索,当以下条件满足任意一个即可:
nums[mid] > nums[mid + 1],因此 mid+1 是最小值。
nums[mid - 1] > nums[mid],因此 mid 是最小值。
*/
public int findMin(int[] nums) {
if (nums.length == 1) return nums[0];
int left = 0, right = nums.length - 1;
if (nums[right] > nums[0]) return nums[0];
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] > nums[mid + 1]) return nums[mid + 1];
if (nums[mid - 1] > nums[mid]) return nums[mid];
if (nums[mid] > nums[0]) {
left = mid + 1;
}
if (nums[mid] < nums[0]) {
right = mid - 1;
}
}
return -1;
}
/*
实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
示例 1: 输入: 4 输出: 2
示例 2: 输入: 8 输出: 2
说明: 8 的平方根是 2.82842...,
由于返回类型是整数,小数部分将被舍去。
*/
public int mySqrt(int x) {
if (x == 0) {
return 0;
}
// 注意:针对特殊测试用例,例如 2147395599
// 要把搜索的范围设置成长整型
long left = 1;
long right = x / 2;
while (left < right) {
// 注意:这里一定取右中位数,如果取左中位数,代码会进入死循环
// long mid = left + (right - left + 1) / 2;
long mid = (left + right + 1) >>> 1;
long square = mid * mid;
if (square > x) {
right = mid - 1;
} else {
left = mid;
}
}
// 因为一定存在,因此无需后处理
return (int) left;
}
/*
给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
示例 1:输入:head = [3,2,0,-4], pos = 1 输出:true
解释:链表中有一个环,其尾部连接到第二个节点
示例 2:输入:head = [1,2], pos = 0 输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:输入:head = [1], pos = -1 输出:false
解释:链表中没有环。
*/
// 方法一
public boolean hasCycle(ListNode head) {
Set<ListNode> nodesSeen = new HashSet<>();
while (head != null) {
if (nodesSeen.contains(head)) {
return true;
} else {
nodesSeen.add(head);
}
head = head.next;
}
return false;
}
// 方法二
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) {
return false;
}
ListNode slow = head;
ListNode fast = head.next;
while (slow != fast) {
if (fast == null || fast.next == null) {
return false;
}
slow = slow.next;
fast = fast.next.next;
}
return true;
}
/*
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
示例: 二叉树:[3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回其层次遍历结果:
[
[3],
[9,20],
[15,7]
]
*/
// 二叉树的层序遍历
void bfs(TreeNode root) {
Queue<TreeNode> queue = new ArrayDeque<>();
queue.add(root);
while (!queue.isEmpty()) {
int n = queue.size();
for (int i = 0; i < n; i++) {
// 变量 i 无实际意义,只是为了循环 n 次
TreeNode node = queue.poll();
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
}
}
}
// 利用层序遍历
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
Queue<TreeNode> queue = new ArrayDeque<>();
if (root != null) {
queue.add(root);
}
while (!queue.isEmpty()) {
int n = queue.size();
List<Integer> level = new ArrayList<>();
for (int i = 0; i < n; i++) {
TreeNode node = queue.poll();
level.add(node.val);
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
}
res.add(level);
}
return res;
}
/*
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例: 输入:n = 3
输出:[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]
*/
List<String> resu = new ArrayList<>();
public List<String> generateParenthesis(int n) {
generate_parenth(0, 0, n, "");
return resu;
}
public void generate_parenth(int leftCount, int rightCount, int n, String s) {
// terminator
if (leftCount == n && rightCount == n) {
resu.add(s);
return;
}
// drill down
if (leftCount < n) {
generate_parenth(leftCount + 1, rightCount, n, s + "(");
}
if (rightCount < leftCount && rightCount < n) {
generate_parenth(leftCount, rightCount + 1, n, s+ ")");
}
}
/*
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母
示例:
输入:"23"
输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
说明:
尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。
*/
public List<String> letterCombinations(String digits) {
if (digits == null || digits.length() == 0) return new ArrayList();
Map<Character, String> map = new HashMap<Character, String>();
map.put('2', "abc");
map.put('3', "def");
map.put('4', "ghi");
map.put('5', "jkl");
map.put('6', "mno");
map.put('7', "pqrs");
map.put('8', "tuv");
map.put('9', "wxyz");
List<String> res = new LinkedList<String>();
search("", digits, 0, res, map);
return res;
}
private void search(String s, String digits, int i, List<String> res, Map<Character, String> map) {
if (i == digits.length()) {
res.add(s);
return;
}
String letter = map.get(digits.charAt(i));
for (int j = 0; j < letter.length(); j++) {
search(s + letter.charAt(j), digits, i + 1, res, map);
}
}
- 236. 二叉树的最近公共祖先
方法一:递归时间复杂度:O(N),其中 N 是二叉树的节点数。二叉树的所有节点有且只会被访问一次,因此时间复杂度为 O(N)。 空间复杂度:O(N) ,其中 N 是二叉树的节点数。递归调用的栈深度取决于二叉树的高度,二叉树最坏情况下为一条链,此时高度为 N,因此空间复杂度为 O(N)。
/*
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
示例 1: 输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3 解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
示例 2: 输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。
说明:
所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉树中。
*/
TreeNode ans = null;
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
dfs(root, p, q);
return ans;
}
private boolean dfs(TreeNode root, TreeNode p, TreeNode q) {
if (root == null) return false;
/*
其中lson 和 rson 分别代表 x 节点的左孩子和右孩子。
说明左子树和右子树均包含 p 节点或 q 节点,如果左子树包含的是 p 节点,那么右子树只能包含 q 节点,反之亦然,因为 p 节点和 q 节点都是不同且唯一的节点,因此如果满足这个判断条件即可说明 x 就是我们要找的最近公共祖先。再来看第二条判断条件,这个判断条件即是考虑了 x 恰好是 p 节点或 q 节点且它的左子树或右子树有一个包含了另一个节点的情况,因此如果满足这个判断条件亦可说明 x 就是我们要找的最近公共祖先
*/
boolean lson = dfs(root.left, p, q);
boolean rson = dfs(root.right, p, q);
if ((lson && rson) || ((root.val == p.val || root.val == q.val) && (lson || rson))) {
ans = root;
}
return lson || rson || (root.val == p.val || root.val == q.val);
}
方法二:存储父节点
复杂度分析
时间复杂度:O(N),其中 NN 是二叉树的节点数。二叉树的所有节点有且只会被访问一次,从 p 和 q 节点往上跳经过的祖先节点个数不会超过 N,因此总的时间复杂度为 O(N)。
空间复杂度:O(N),其中 N是二叉树的节点数。递归调用的栈深度取决于二叉树的高度,二叉树最坏情况下为一条链,此时高度为 N,因此空间复杂度为 O(N),哈希表存储每个节点的父节点也需要 O(N) 的空间复杂度,因此最后总的空间复杂度为 O(N)。
/*
思路: 我们可以用哈希表存储所有节点的父节点,然后我们就可以利用节点的父节点信息从 p 结点开始不断往上跳,并记录已经访问过的节点,再从 q 节点开始不断往上跳,如果碰到已经访问过的节点,那么这个节点就是我们要找的最近公共祖先。
算法:
从根节点开始遍历整棵二叉树,用哈希表记录每个节点的父节点指针。
从 p 节点开始不断往它的祖先移动,并用数据结构记录已经访问过的祖先节点。
同样,我们再从 q 节点开始不断往它的祖先移动,如果有祖先已经被访问过,即意味着这是 p 和 q 的深度最深的公共祖先,即 LCA 节点。
*/
Map<Integer, TreeNode> parent = new HashMap<Integer, TreeNode>();
Set<Integer> visited = new HashSet<Integer>();
public void dfs(TreeNode root) {
if (root.left != null) {
parent.put(root.left.val, root);
dfs(root.left);
}
if (root.right != null) {
parent.put(root.right.val, root);
dfs(root.right);
}
}
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
dfs(root);
while (p != null) {
visited.add(p.val);
p = parent.get(p.val);
}
while (q != null) {
if (visited.contains(q.val)) {
return q;
}
q = parent.get(q.val);
}
return null;
}
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
示例:
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
方法一 递归回溯
回溯思想
思想: 1、什么是“树形问题”?为什么为什么是在树形问题上使用“深度优先遍历”?不用深度优先遍历我们还可以用什么?
2、什么是“回溯”?为什么需要回溯?
3、不回溯可以吗?
首先介绍“回溯”算法的应用。“回溯”算法也叫“回溯搜索”算法,主要用于在一个庞大的空间里搜索我们所需要的问题的解。我们每天使用的“搜索引擎”就是帮助我们在庞大的互联网上搜索我们需要的信息。“搜索”引擎的“搜索”和“回溯搜索”算法的“搜索”意思是一样的。
“回溯”指的是“状态重置”,可以理解为“回到过去”、“恢复现场”,是在编码的过程中,是为了节约空间而使用的一种技巧。而回溯其实是“深度优先遍历”特有的一种现象。之所以是“深度优先遍历”,是因为我们要解决的问题通常是在一棵树上完成的,在这棵树上搜索需要的答案,一般使用深度优先遍历。
“全排列”就是一个非常经典的“回溯”算法的应用。我们知道,N 个数字的全排列一共有 N! 这么多个。
尝试一下在纸上写 3 个数字、4 个数字、5 个数字的全排列,相信不难找到这样的方法。
以数组 [1, 2, 3] 的全排列为例。
我们先写以 1 开头的全排列,它们是:[1, 2, 3], [1, 3, 2];
再写以 2 开头的全排列,它们是:[2, 1, 3], [2, 3, 1];
最后写以 3 开头的全排列,它们是:[3, 1, 2], [3, 2, 1]。
我们只需要按顺序枚举每一位可能出现的情况,已经选择的数字在接下来要确定的数字中不能出现。按照这种策略选取就能够做到不重不漏,把可能的全排列都枚举出来。
在枚举第一位的时候,有 3 种情况。
在枚举第二位的时候,前面已经出现过的数字就不能再被选取了;
在枚举第三位的时候,前面 2 个已经选择过的数字就不能再被选取了。
这样的思路,我们可以用一个树形结构表示。看到这里的朋友,建议自己先尝试画一下“全排列”问题的树形结构。
使用编程的方法得到全排列,就是在这样的一个树形结构中进行编程,具体来说,就是执行一次深度优先遍历,从树的根结点到叶子结点形成的路径就是一个全排列。
说明:
1、每一个结点表示了“全排列”问题求解的不同阶段,这些阶段通过变量的“不同的值”体现;
2、这些变量的不同的值,也称之为“状态”;
3、使用深度优先遍历有“回头”的过程,在“回头”以后,状态变量需要设置成为和先前一样;
4、因此在回到上一层结点的过程中,需要撤销上一次选择,这个操作也称之为“状态重置”;
5、深度优先遍历,可以直接借助系统栈空间,为我们保存所需要的状态变量,在编码中只需要注意遍历到相应的结点的时候,状态变量的值是正确的,具体的做法是:往下走一层的时候,path 变量在尾部追加,而往回走的时候,需要撤销上一次的选择,也是在尾部操作,因此 path 变量是一个栈。
6、深度优先遍历通过“回溯”操作,实现了全局使用一份状态变量的效果。
下面我们解释如何编码:
1、首先这棵树除了根结点和叶子结点以外,每一个结点做的事情其实是一样的,即在已经选了一些数的前提,我们需要在剩下还没有选择的数中按照顺序依次选择一个数,这显然是一个递归结构;
2、递归的终止条件是,数已经选够了,因此我们需要一个变量来表示当前递归到第几层,我们把这个变量叫做 depth;
3、这些结点实际上表示了搜索(查找)全排列问题的不同阶段,为了区分这些不同阶段,我们就需要一些变量来记录为了得到一个全排列,程序进行到哪一步了,在这里我们需要两个变量:
(1)已经选了哪些数,到叶子结点时候,这些已经选择的数就构成了一个全排列;
(2)一个布尔数组 used,初始化的时候都为 false 表示这些数还没有被选择,当我们选定一个数的时候,就将这个数组的相应位置设置为 true ,这样在考虑下一个位置的时候,就能够以 O(1) 的时间复杂度判断这个数是否被选择过,这是一种“以空间换时间”的思想。
我们把这两个变量称之为“状态变量”,它们表示了我们在求解一个问题的时候所处的阶段。
4、在非叶子结点处,产生不同的分支,这一操作的语义是:在还未选择的数中依次选择一个元素作为下一个位置的元素,这显然得通过一个循环实现。
5、另外,因为是执行深度优先遍历,从较深层的结点返回到较浅层结点的时候,需要做“状态重置”,即“回到过去”、“恢复现场”,我们举一个例子。
从 [1, 2, 3] 到 [1, 3, 2] ,深度优先遍历是这样做的,从 [1, 2, 3] 回到 [1, 2] 的时候,需要撤销刚刚已经选择的数 3,因为在这一层只有一个数 3 我们已经尝试过了,因此程序回到上一层,需要撤销对 2 的选择,好让后面的程序知道,选择 3 了以后还能够选择 2。
这种在遍历的过程中,从深层结点回到浅层结点的过程中所做的操作就叫“回溯”。
public List<List<Integer>> permute(int[] nums) {
int len = nums.length;
List<List<Integer>> res = new ArrayList<>();
if (len == 0) {
return res;
}
Deque<Integer> stack = new ArrayDeque<Integer>();
boolean[] used = new boolean[len]; // 默认初始化为false
dfs(nums, len, 0, stack, used, res);
return res;
}
public void dfs(int[] nums, int len, int depth, Deque<Integer> stack, boolean[] used, List<List<Integer>> res) {
if (len == depth) {
res.add(new ArrayList<>(stack));
return;
}
for (int i = 0; i < len; i++) {
if (used[i]) {
continue;
}
stack.addLast(nums[i]);
used[i] = true;
dfs(nums, len, depth + 1, stack, used, res);
// 注意:这里是状态重置,是从深层结点回到浅层结点的过程,代码在形式上和递归之前是对称的
stack.removeLast();
used[i] = false;
}
}
方法二 递归不需回溯的方法
public List<List<Integer>> permute(int[] nums) {
int len = nums.length;
List<List<Integer>> res = new ArrayList<>();
if (len == 0) {
return res;
}
List<Integer> stack = new ArrayList<Integer>();
boolean[] used = new boolean[len]; // 默认初始化为false
dfs(nums, len, 0, stack, used, res);
return res;
}
public void dfs(int[] nums, int len, int depth, List<Integer> stack, boolean[] used, List<List<Integer>> res) {
if (len == depth) {
// 3、不用拷贝,因为每一层传递下来的 path 变量都是新建的
res.add(stack);
return;
}
for (int i = 0; i < len; i++) {
if (used[i]) {
continue;
}
// 1、每一次尝试都创建新的变量表示当前的"状态"
List<Integer> newStack = new ArrayList<>(stack);
newStack.add(nums[i]);
boolean[] newUsed = new boolean[len];
System.arraycopy(used, 0, newUsed, 0, len);
newUsed[i] = true;
dfs(nums, len, depth + 1, newStack, newUsed, res);
// 2、无需回溯
}
}
方法三
public void backtrack(int n,
ArrayList<Integer> output,
List<List<Integer>> res,
int first) {
// 所有数都填完了
if (first == n)
res.add(new ArrayList<Integer>(output));
for (int i = first; i < n; i++) {
// 动态维护数组
Collections.swap(output, first, i);
// 继续递归填下一个数
backtrack(n, output, res, first + 1);
// 撤销操作
Collections.swap(output, first, i);
}
}
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> res = new LinkedList();
ArrayList<Integer> output = new ArrayList<Integer>();
for (int num : nums)
output.add(num);
int n = nums.length;
backtrack(n, output, res, 0);
return res;
}
/*
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。
每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。
输入: 4
输出: [
[".Q..", // 解法 1
"...Q",
"Q...",
"..Q."],
["..Q.", // 解法 2
"Q...",
"...Q",
".Q.."]
]
解释: 4 皇后问题存在两个不同的解法。
*/