LeetCode Top100之226,234,283,437,438,448,461,538题(常见的位操作)

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=ab=(a+b)b=a
    a = a − b = ( a + b ) − a = b a=a-b=(a+b)-a=b a=ab=(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为例:
  1. 原码: 1000   0000   0000   0000   0000   0000   0000   0100 1000\ 0000\ 0000\ 0000\ 0000\ 0000\ 0000\ 0100 1000 0000 0000 0000 0000 0000 0000 0100
  2. 补码: 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 11=1
    0 ∣ 1 = 1 0|1=1 01=1 要置1的bit
    0 ∣ 0 = 0 0|0=0 00=0
    1 ∣ 0 = 1 1|0=1 10=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]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值