剑指offer刷题宝典--第4节

七、排序

剑指 Offer 03. 数组中重复的数字

剑指 Offer 03. 数组中重复的数字

在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

方法二:原地交换

遍历中,第一次遇到数字 xx 时,将其交换至索引 xx 处;而当第二次遇到数字 xx 时,一定有 nums[x] = xnums[x]=x ,此时即可得到一组重复数字。

时间复杂度 O(N)
空间复杂度 O(1)

在这里插入图片描述

class Solution {
    public int findRepeatNumber(int[] nums) {
        HashSet<Integer> set = new HashSet<>();
        int res = 0;
        for (int num : nums) {
            if (set.contains(num)) {
                res = num;
            } else {
                set.add(num);
            }
        }
        return res;
    }
}



class Solution {
    public int findRepeatNumber(int[] nums) {
        int res = 0;
        int n = nums.length;
        int i = 0;
        while (i < n) {
            int inx = nums[i];
            // 正确位置
            if (inx == i) {
                i++;
                continue;
            }
            // 重复数字
            if (nums[inx] == inx) {
                return inx;
            }
            // 非重复数字
            else {
                nums[i] = nums[inx];
                nums[inx] = inx;
            }
        }
        return -1;
    }
}

剑指 Offer 40. 最小的k个数

剑指 Offer 40. 最小的k个数

解题思路:
对于经典TopK问题,本文给出 4 种通用解决方案。

一、用快排+切分最最最高效解决 TopK 问题:

这里说的快排,应该是利用了快排思想的快速选择

快排切分时间复杂度分析: 因为我们是要找下标为k的元素,第一次切分的时候需要遍历整个数组 (0 ~ n) 找到了下标是 j 的元素,假如 k 比 j 小的话,那么我们下次切分只要遍历数组 (0~k-1)的元素就行啦,反之如果 k 比 j 大的话,那下次切分只要遍历数组 (k+1~n) 的元素就行啦,总之可以看作每次调用 partition 遍历的元素数目都是上一次遍历的 1/2,因此时间复杂度是 N + N/2 + N/4 + … + N/N = 2N, 因此时间复杂度是 O(N)

//方式一
class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        if(k==0) return new int[0];
        int n = arr.length;
        return quickSort(arr, 0, n - 1, k);
    }

    private int[] quickSort(int[] arr, int l, int r, int k) {
        int pivot = arr[l];
        int left = l;
        int right = r;

        while (left < right) {
            while (left < right && arr[right] >= pivot) right--;
            arr[left] = arr[right];
            while (left < right && arr[left] <= pivot) left++;
            arr[right] = arr[left];
        }
        arr[left] = pivot;
        if (left > k - 1) {
            return quickSort(arr, l, left - 1, k);
        } else if (left < k - 1) {
            return quickSort(arr, left + 1, r, k);
        }else
            return Arrays.copyOfRange(arr, 0, left + 1);
    }
}

二、大根堆(前 K 小) / 小根堆(前 K 大)
本题是求前 K 小,因此用一个容量为 K 的大根堆,每次 poll 出最大的数,那堆中保留的就是前 K 小啦(注意不是小根堆!小根堆的话需要把全部的元素都入堆,那是 O(NlogN),就不是 O(NlogK))

// 保持堆的大小为K,然后遍历数组中的数字,遍历的时候做如下判断:
// 1. 若目前堆的大小小于K,将当前数字放入堆中。
// 2. 否则判断当前数字与大根堆堆顶元素的大小关系,如果当前数字比大根堆堆顶还大,这个数就直接跳过;
//    反之如果当前数字比大根堆堆顶小,先poll掉堆顶,再将该数字放入堆中。
class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        if (k == 0 || arr.length == 0) {
            return new int[0];
        }
        // 默认是小根堆,实现大根堆需要重写一下比较器。
        Queue<Integer> pq = new PriorityQueue<>((v1, v2) -> v2 - v1);
        for (int num: arr) {
            if (pq.size() < k) {
                pq.offer(num);
            } else if (num < pq.peek()) {
                pq.poll();
                pq.offer(num);
            }
        }
        
        // 返回堆中的元素
        int[] res = new int[pq.size()];
        int idx = 0;
        for(int num: pq) {
            res[idx++] = num;
        }
        return res;
    }
}

八、位运算

剑指 Offer 16. 数值的整数次方

剑指 Offer 16. 数值的整数次方

快速幂

https://leetcode.cn/problems/shu-zhi-de-zheng-shu-ci-fang-lcof/solution/jian-dan-li-jie-kuai-su-mi-by-ollieq-rl74/

在这里插入图片描述

class Solution {
    public double myPow(double x, int n) {
        if(x == 0) return 0;
        long b = n;
        double res = 1.0;
        if(b < 0) {
            x = 1 / x;
            b = -b;
        }
        while(b > 0){
            // 最后一位为1,需要乘上该位上的权重
            if((b & 1) == 1){
                res *= x;
            }
            x *= x;
            b >>= 1;
        }
        return res;
    }
}

JZ65 不用加减乘除做加法

进阶:空间复杂度 O(1),时间复杂度 O(1)

使用位运算实现加法。

public class Solution {
    public int Add(int num1, int num2) {
        //无进位和等于异或结果   进位等于与结果左移一位
        int sum = num1 ^ num2;
        int carry = (num1 & num2) << 1;
        while (carry != 0) {
            int a = sum;
            int b = carry;
            sum = a ^ b;
            carry = (a & b) << 1;
        }
        return sum;
    }
}

JZ15 二进制中1的个数

// >>>无符号右移,不管正数还是负数,高位都用0补齐(忽略符号位)
// >>,有符号右移位,将运算数的二进制整体右移指定位数,整数高位用0补齐,负数高位用1补齐(保持负数符号不变)。
public class Solution {
    public int NumberOf1(int n) {
        int count = 0;
        while (n != 0) {
            count += (n & 1);
            n >>>= 1;
        }
        return count;
    }
}

JZ56 数组中只出现一次的两个数字

要求:空间复杂度 O(1),时间复杂度 O(n)

简化问题: 一个整型数组 nums 里除 一个 数字之外,其他数字都出现了两次。

当只有一个出现了一次的数字的时候,则只需要将全部数进行异或运算,运算结果就剩下了那个只出现一次的数字了。

假设数组异或的二进制结果为10010,那么说明这两个数从右向左数第2位是不同的

那么可以根据数组里面所有数的第二位为0或者1将数组划分为2个。

这样做可以将目标数必然分散在不同的数组中,而且相同的数必然落在同一个数组中。

这两个数组里面的数各自进行异或,得到的结果就是答案

public int[] singleNumber(int[] nums) {
    int x = 0;
    for(int num : nums)  // 1. 遍历 nums 执行异或运算
        x ^= num;
    return x;            // 2. 返回出现一次的数字 x
}
class Solution {
    public int[] singleNumbers(int[] nums) {
        int n = nums.length;
        int s = 0;
        for (int num : nums) {
            s ^= num;
        }
        
        int m = 1;
        while ((s & m) == 0) {
            m <<= 1;
        }

        int x = 0;
        int y = 0;
        for (int num : nums) {
            if ((num & m) != 0) {
                x ^= num;
            } else
                y ^= num;
        }
        return new int[]{x, y};
    }
}

剑指 Offer 56 - II. 数组中数字出现的次数 II

剑指 Offer 56 - II. 数组中数字出现的次数 II

方法二:遍历统计

​ 考虑数字的二进制形式,对于出现三次的数字,各 二进制位 出现的次数都是 3 的倍数。因此,统计所有数字的各二进制位中 1的出现次数,并对 3求余,结果则为只出现一次的数字。

在这里插入图片描述

class Solution {
    public int singleNumber(int[] nums) {
        int[] bits = new int[32];
        int res = 0;
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            int j = 0;
            while (nums[i] != 0) {
                bits[j] += nums[i] & 1;
                nums[i] >>>= 1;
                j++;
            }
        }

        for (int i = 0; i < 32; i++) {
            int bit = bits[i] % 3;
            res += bit * (1 << i);
        }
        return res;
    }
}

JZ64 求1+2+3+…+n

进阶: 空间复杂度 O(1) ,时间复杂度 O(n)

class Solution {
    int res = 0;

    public int sumNums(int n) {
        boolean x = n > 1 && sumNums(n - 1) > 0;
        res += n;
        return res;
    }
}

整理不易🚀🚀,关注和收藏后拿走📌📌欢迎留言🧐👋📣
欢迎专注我的公众号AdaCoding 和 Github:AdaCoding123
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值