剑指offer刷题宝典--第4节
七、排序
剑指 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个数
解题思路:
对于经典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. 数值的整数次方
快速幂
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