- 写于2019年6月9日 - 6月10日
文章目录
- [226. 翻转二叉树](https://leetcode.com/problems/invert-binary-tree/)
- [234. 回文链表](https://leetcode.com/problems/palindrome-linked-list/)
- [283. 移动零](https://leetcode.com/problems/move-zeroes/)
- [437. 路径总和 III](https://leetcode.com/problems/path-sum-iii/)
- [438. 找到字符串中所有字母异位词](https://leetcode.com/problems/find-all-anagrams-in-a-string/)
- [448. 找到所有数组中消失的数字](https://leetcode.com/problems/find-all-numbers-disappeared-in-an-array/)
- [461. 汉明距离](https://leetcode.com/problems/hamming-distance/)
- [538. 把二叉搜索树转换为累加树](https://leetcode.com/problems/convert-bst-to-greater-tree/)
226. 翻转二叉树
① 题目描述
中文描述:https://leetcode-cn.com/problems/invert-binary-tree/
② 自己的想法:递归,从上到下,交换左右孩子节点
- 先交换根节点的左右孩子节点,再递归对左右子树进行交换操作。
- 代码如下,运行时间
0ms
:
public TreeNode invertTree(TreeNode root) {
if (root == null) {
return root;
}
if (root.left == null && root.right == null) {
return root;
}
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
invertTree(root.left);
invertTree(root.right);
return root;
}
③ 别人的递归:先翻转左右子树,在交换左右子树
- 翻转一棵二叉树,就是先翻转左子树和右子树,然后交换左右子树。
- 代码如下,运行时间
0ms
:
public TreeNode invertTree(TreeNode root) {
if (root == null) {
return root;
}
TreeNode rigth = invertTree(root.right);
TreeNode left = invertTree(root.left);
root.left = rigth;
root.right = left;
return root;
}
④ 迭代
- 与自己的递归思想很像,分别交换每一个节点的左右孩子节点。
- 代码如下,运行时间
0ms
:
public TreeNode invertTree(TreeNode root) {
if (root == null) {
return root;
}
Queue<TreeNode> queue=new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()){
TreeNode node=queue.poll();
TreeNode temp=node.left;
node.left=node.right;
node.right=temp;
if (node.left!=null){
queue.add(node.left);
}
if (node.right!=null){
queue.add(node.right);
}
}
return root;
}
234. 回文链表
① 题目描述
中文题目:https://leetcode-cn.com/problems/palindrome-linked-list/
② 自己的想法:将链表中的元素存入List中进行判断
- 一定要注意: list中元素的判断相等,最好使用
equals
,最近发现针对Intege
只要是负数,都无法通过==
进行相等判断! - 时间复杂度 O ( n + n 2 ) O(n+\frac{n}{2}) O(n+2n),空间复杂度 O ( n ) O(n) O(n)
- 代码如下,运行时间
3ms
:
public boolean isPalindrome(ListNode head) {
if (head == null || head.next == null) {
return true;
}
List<Integer> list=new ArrayList<>();
while (head!=null){
list.add(head.val);
head=head.next;
}
int start=0,end=list.size()-1;
while(start<=end){
if (!list.get(start).equals(list.get(end))){
return false;
}
start++;
end--;
}
return true;
}
③ 使用stack
- 如果链表是回文链表,顺序遍历压入栈后,再顺序遍历比较栈中节点值和链表节点值使用=否一致。
- 因为:将链表节点压入栈相当于实现了链表的逆序。而回文是正序和逆序均相同。
- 代码如下,运行时间
3ms
:
public boolean isPalindrome(ListNode head) {
if (head == null || head.next == null) {
return true;
}
Stack<ListNode> stack = new Stack<>();
ListNode p = head;
while (p != null) {
stack.push(p);
p = p.next;
}
while (head != null) {
if (head.val != stack.pop().val) {
return false;
}
head = head.next;
}
return true;
}
283. 移动零
① 题目描述
中文题目:https://leetcode-cn.com/problems/move-zeroes/
② 借助List获取非零元素
- 借助List获取非零元素,然后将非零元素添加在数组头部,将剩余位置置零。
- 时间复杂度 O ( 2 n ) O(2n) O(2n),空间复杂度最大为 O ( n ) O(n) O(n),数组中全部为非零元素。没有实现原地移动!
- 代码如下,运行时间
1ms
:
public void moveZeroes(int[] nums) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < nums.length; i++) {
if (nums[i] != 0) {
list.add(nums[i]);
}
}
for (int i = 0; i < list.size(); i++) {
nums[i] = list.get(i);
}
for (int i = list.size(); i < nums.length; i++) {
nums[i] = 0;
}
}
③ 双指针
- 指针p,指向待插入非零元素的位置;cur指针从前往后遍历,发现非零元素,便将其移动到p指针所在的位置。
-
- 时间复杂度 O ( n ) O(n) O(n),空间复杂度最大为 O ( 2 ) O(2) O(2)。实现了原地移动!
- 代码如下,运行时间
0ms
:
public void moveZeroes(int[] nums) {
int p = 0, cur = 0;
for (; cur < nums.length; cur++) {
if (nums[cur] != 0) {
nums[p] = nums[cur];
if (p != cur) {
nums[cur] = 0;
}
p++;
}
}
}
437. 路径总和 III
① 题目描述
中文题目:https://leetcode-cn.com/problems/path-sum-iii/
② 递归
- 以当前节点作为路径的起始节点,采用dfs查找满足条件的路径,还需要递归以当前节点的左孩子节点、右孩子节点作为起始节点的情况。
- 代码如下,运行时间
10ms
:
public int pathSum(TreeNode root, int sum) {
if (root == null) {
return 0;
}
return dfs(root, sum) + pathSum(root.right, sum) + pathSum(root.left, sum);
}
public int dfs(TreeNode root, int target) {
if (root == null) {
return 0;
}
int count = 0;
if (root.val == target) {// 以root作为根节点,可以向左向右查找合适的path;如果root刚好符合,将其计入path总数中
count = 1;
}
// root没有满足,需要向左、向右查找新的节点;返回root是否满足的情况以及向左、向右的path数目
return count + dfs(root.left, target - root.val) + dfs(root.right, target - root.val);
}
438. 找到字符串中所有字母异位词
① 题目描述
中文题目:https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/
② 自己的想法:获取子串,比较排序后的子串与目标串是否一致。
- 通过java API,现将字符串转为
char[]
,然后对char[]
进行排序后,又利用String.valueOf()
转换回字符串。 - 不停地在s中截取长度与p一致的子串,比较排序后的子串是否一致。如果一致,记录下标。
- 代码如下,运行时间
1176ms
:
public List<Integer> findAnagrams(String s, String p) {
List<Integer> list = new ArrayList<>();
char[] pChar = p.toCharArray();
Arrays.sort(pChar);
p = String.valueOf(pChar);
for (int i = 0; i <= s.length() - p.length(); i++) {
char[] temp = s.substring(i, i + p.length()).toCharArray();
Arrays.sort(temp);
if (p.equals(String.valueOf(temp))) {
list.add(i);
}
}
return list;
}
③ 双指针、滑块
- 准备工作:
① 设置两个指针 起始坐标start=0,一个结束坐标end=0
② 建立两个长度为26的int数组,arr1记录p里面的字母分别都有多少个,arr2记录两个指针中的字母分别都有多少个 - 正式开始:
① 做一个循环,start先不动,拿到end指针对应的字母,arr2中字母对应的数量加1,并让end自增指向下一个字母
② 判断一下,这个字母的数字是不是比arr1中要多了,如果是,表示出现了下面两种情况中的一种
1. start到end中的字母数量比p的length大,那么肯定至少会有一个字母的数量比p_letter里多
2. start到end的距离小于等于p的length,但是里面的某一个字母比p多
不管是上述情况中哪一种,start都应该前进,直到这个字母的数量等于p中的字母数量。(做完这一步start和end之间的字母数量一定小于等于p的length)
③ 然后再判断一下,start和end之间的字母数量是不是等于p的length。如果是,表示两个坐标之间的字母和p的字母构成一样。PS:
因为end指针始终指向下一个字母,因此求end和start之间的字母个数,直接end - start
就可以! - 因为第二步中,一出现start和end之间的字母比p多,我们就让start前进,直到这个字母数量等于p里面的数量,确保了没有任何一个字母比p里面多。
- 当start和end之间的字母数量和p里面的一样,且start和end之间没有任何一个字母比p多,就说明他们的字母组成一模一样。
- 因为数量一样的情况下,如果出现某个字母比p少,就一定会有另外一个字母比p多,这是充要条件。
- 代码如下,运行时间
5ms
:
public List<Integer> findAnagrams(String s, String p) {
List<Integer> list = new ArrayList<>();
int start = 0;
int end = 0;
int[] arr1 = new int[26];
for (int i = 0; i < p.length(); i++) {
int index = p.charAt(i) - 'a';
arr1[index] += 1;
}
int[] arr2 = new int[26];
while (end < s.length()) {
int index = s.charAt(end) - 'a';
arr2[index] += 1;
end++;
while (arr2[index] > arr1[index]) {// 移动start指针让字母个数一致
int temp = s.charAt(start) - 'a';
arr2[temp]--;
start++;
}
if (end - start == p.length()) {
list.add(start);
}
}
return list;
}
448. 找到所有数组中消失的数字
① 题目描述
中文题目:https://leetcode-cn.com/problems/find-all-numbers-disappeared-in-an-array/
② 自己的想法:借助布尔数组,标记数字的出现
- 通过使用布尔数组,出现的数字对应的元素(下标
num - 1
)标记为true
。通过遍历布尔数组,将值为false
的下标+1
存入结果List中。 - 使用了二外的空间,空间复杂度 O ( n ) O(n) O(n)。
- 代码如下,运行时间
4ms
:
public List<Integer> findDisappearedNumbers(int[] nums) {
List<Integer> list = new ArrayList<>();
boolean[] flag = new boolean[nums.length];
for (int i = 0; i < nums.length; i++) {
flag[nums[i] - 1] = true;
}
for (int i = 0; i < flag.length; i++) {
if (!flag[i]) {
list.add(i + 1);
}
}
return list;
}
③ 数组元素交换(非常巧妙的交换方式)
- 我们假设数组序号
0 - n-1
应该放的是数字1~n
.则遍历数组,将当前元素与其应该放的位置处的元素交换,知道该位置处放的是应该放的元素或者与交换处元素相等。 - 当位置元素不正确时需要进行元素交换,交换方式很奇妙:
a = a + b a=a+b a=a+b
b = a − b = ( a + b ) − b = a b=a-b=(a+b)-b=a b=a−b=(a+b)−b=a
a = a − b = ( a + b ) − a = b a=a-b=(a+b)-a=b a=a−b=(a+b)−a=b - 注意: 只有当元素放置在正确的位置时,才增加i,查找下一个元素。
- 代码如下,运行时间
7ms
。竟然无论是时间还是空间貌似都没有自己的方法好!
public List<Integer> findDisappearedNumbers(int[] nums) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < nums.length; ) {
if (nums[i] == i + 1 || nums[i] == nums[nums[i] - 1]) {
i++;
} else {
swap(nums, i, nums[i] - 1);
}
}
for (int i = 0; i < nums.length; i++) {
if (nums[i] != i + 1) {
list.add(i + 1);
}
}
return list;
}
public void swap(int[] nums, int i, int j) {// 非常巧妙的元素交换
nums[i] = nums[i] + nums[j];
nums[j] = nums[i] - nums[j];
nums[i] = nums[i] - nums[j];
}
461. 汉明距离
① 题目描述
中文题目:https://leetcode-cn.com/problems/hamming-distance/
② 自己想法:位操作,比较两个数每一bit是否相同
- 通过获取两个数的每一bit,比较对应的bit是否相同,计算汉明距离
- 代码如下,运行时间
0ms
:
public int hammingDistance(int x, int y) {
int dis = 0;
while (x != 0 || y != 0) {
int t1 = x & 1;// 取最后一个bit,如果该bit是0,t1为0;否则,t1为1
int t2 = y & 1;
if (t1 != t2) {
dis++;
}
x = x >> 1;// 右移,判断下一个bit
y = y >> 1;
}
return dis;
}
③ 先异或,再通过汉明重量计算汉明距离
- 汉明重量: 二进制中1的个数
- 汉明距离: 两个二进制中位数不同的个数
- 汉明重量计算:
n &= n - 1
这个操作对比当前操作位高的位没有影响,对低位则完全清零。
拿6(110)来做例子,
第一次110 & 101=100
,这次操作成功的把从低位起第一个1消掉了,同时计数器加1。
第二次100 & 011=000
,同理又统计了高位的一个1,此时n已变为0,不需要再继续了,于是110中有2个1。 - 汉明距离计算:
先将两个数进行异或^
(相同为0不同为1)运算
再将异或完的数 计算汉明重量,也就是计算有多少个不同的数, 计算共多少个1 - 代码如下,运行时间
0ms
:
public int hammingDistance(int x, int y) {
int dis = 0;
int n = x ^ y; // 计算出两个数的不同bit
while (n != 0) {// 计算n中bit为1的个数
dis++;
n = n & (n - 1);
}
return dis;
}
④ 总结:如何统计一个数二进制形式中1的个数?
1. n = n &(n - 1)
计算汉明重量
while (n != 0) {// 计算n中bit为1的个数
count++;
n = n & (n - 1);
}
2. 先与1再右移
- 有种很直观的想法,我获取n中每一个bit,看其是否为1。
- 通过 n & 1 n\&1 n&1便可以获取最低位的bit;然后将n右移一位,使得每一个待获取的bit都处于最低位。直到n变成0,说明里面没有bit为1了。
while (x != 0 ) {
int t1 = x & 1;// 取最后一个bit,如果该bit是0,t1为0;否则,t1为1
if (t1 == 1) {
dis++;
}
x = x >> 1;// 右移,判断下一个bit
}
3. 考虑整数有负数的情况:利用对flag左移,来直接判断每一位是否为1
- 如果是负数右移运算,在计算机中使用补码表示。以
-4
为例:
- 原码: 1000 0000 0000 0000 0000 0000 0000 0100 1000\ 0000\ 0000\ 0000\ 0000\ 0000\ 0000\ 0100 1000 0000 0000 0000 0000 0000 0000 0100
- 补码: 1111 1111 1111 1111 1111 1111 1111 1100 1111\ 1111\ 1111\ 1111\ 1111\ 1111\ 1111\ 1100 1111 1111 1111 1111 1111 1111 1111 1100
- 如果仍然使用
先与1再右移
的方式,最高位将会补1。而使用无符号右移又会使数字发生改变。 - 所以,可以直接计算每一位上是否为1。对
-4
的二进制中1的个数计算结果为30
,与-4
补码中的1的个数一致。
int count = 0;
int flag = 1;
while (flag != 0) {
// 直接针对每1 bit,最后结果可能为1,2,4.并非针对末尾时的1
if ((n & flag) !=0) {
count++;
}
flag=flag<<1;
}
⑤ 总结:如何统计一个数的二进制形式中0的个数?
- 如果针对正整数和零,要求不能计算二进制形式中的前导0,则使用方法一。
- 如果针对正整数和零,要求计算二进制形式中前导0,则使用方法二和方法三。
1. 根据直接判断每一位是否为1的灵感
- 在整数是负数的情况下,通过flag左移来判断整数中每一位是否为1。
- 对于负数,也可以使用该方法,使计数变量
count
加1的情况为n & flag
为0。 - 但是对正数或零,却不能采用这样的方法。因为正数或零除了真实的数据位,高位都是0,这样会计算出多余的0。
- 可以考虑通过 n % 2 n\%2 n%2,如果余数为0计数变量加1,再 n / 2 n/2 n/2来进行计算。
int count = 0;
int flag = 1;
if (n < 0) {
while (flag != 0) {
if ((n & flag) == 0) {
count++;
}
flag = flag << 1;
}
return count;
}
while (n != 0) {
int temp = n % 2;
if (temp == 0) {
count++;
}
n = n / 2;
}
return count;
2. 与汉明距离计算类似的方式
- 这种方式针对正整数和零,会计算二进制形式中的前导0。
int count = 0;
while((n+1)!=0){
count++;
n=n|(n+1);
}
3. 能计算前导0,直接使用对flag左移的方式
int count = 0;
int flag = 1;
while(flag!=0){
if ((n&flag)==0){
count++;
}
flag=flag<<1;
}
⑥ 总结:获取第i位、第i位置1、第i位清零?
1. 如何获取一个数二进制形式中第i位?
- 要获取第i位是0还是1,将这个数与第i位为1,其他位为0的数相与,便可知道该数的第i位是0还是1。
1 & 1 = 1 1 \& 1=1 1&1=1
0 & 1 = 0 0 \& 1=0 0&1=0 - 假设第i位是从右往左算起,起始下标为0。
int temp = x & (1 << i);
return temp;
2. 如何将一个数二进制形式中第i位置1?
- 要将第i位置1,将这个数与第i为1,其他位为0的数相或,便可将第i位置1。
1 ∣ 1 = 1 1|1=1 1∣1=1
0 ∣ 1 = 1 0|1=1 0∣1=1 要置1的bit
0 ∣ 0 = 0 0|0=0 0∣0=0
1 ∣ 0 = 1 1|0=1 1∣0=1不发生改变的bit - 假设第i位是从右往左算起,起始下标为0。
return x | (1 << i);
3. 如何将一个数二进制形式中第i位清0?
- 要将第i位清0,将这个数与第i为0,其他位为1的数相与,便可将第i位清零。
- 假设第i位是从右往左算起,起始下标为0。
return n & (~(1 << i));
538. 把二叉搜索树转换为累加树
① 题目描述
中文描述:https://leetcode-cn.com/problems/convert-bst-to-greater-tree/
② 自己的想法:先获取val,再更改val
- 通过某种方式遍历二叉查找树,将val存放到List中;然后再以某种方式遍历二叉查找树,对每一个节点的val与List中的元素进行大小比较,决定是否更改其val。
- 代码如下,运行时间
116ms
:
public TreeNode convertBST(TreeNode root) {
if (root == null) {
return null;
}
List<Integer> list = new ArrayList<>();
InorderTree(root, list);
GreaterTree(root, list);
return root;
}
public void InorderTree(TreeNode root, List<Integer> list) {
if (root == null) {
return;
}
InorderTree(root.left, list);
list.add(root.val);
InorderTree(root.right, list);
}
public void GreaterTree(TreeNode root, List<Integer> list) {
if (root == null) {
return;
}
GreaterTree(root.left, list);
int temp = root.val;// temp参与大小比较,满足条件时直接更改root.val
for (int i = 0; i < list.size(); i++) {
if (list.get(i) > temp) {
root.val += list.get(i);
}
}
GreaterTree(root.right, list);
}
- 注意: 一定要用temp存储原始的
root.val
,比较大小时使用temp进行比较,更新时直接对root.val
进行操作。失败的case:
Input: [2,1,3]
Output:[5,3,3]
Expected:[5,6,3]