【LeetCode】《剑指Offer》第Ⅶ篇⊰⊰⊰ 56 - 60题
文章目录
56 - I. 数组中数字出现的次数(easy)
【题目】一个整型数组 nums
里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
【示例】
输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]
【解题思路】
位运算异或^
a ^ a = 0
a ^ 0 = a
class Solution {
public int[] singleNumbers(int[] nums) {
int eo = 0;
for (int num : nums) {
eo ^= num;
}
//找到最后一个1
int r = (eo & (-eo));
int e = eo;
for (int num : nums) {
if ((num & r) > 0) {
e ^= num;
}
}
return new int[]{e, e ^ eo};
}
}
56 - II. 数组中数字出现的次数 II(medium)
剑指 Offer 56 - II. 数组中数字出现的次数 II
【题目】在一个数组 nums
中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
限制:
1 <= nums.length <= 10000
1 <= nums[i] < 2^31
【示例】
输入:nums = [9,1,7,9,7,9,7]
输出:1
【解题思路】
方法一:比特位计数
class Solution {
public int singleNumber(int[] nums) {
int[] bit = new int[32];
for (int num : nums) {
for (int i = 0; i < 32; i++) {
if ((num & (1 << i)) > 0) {
bit[i] += 1;
}
}
}
int res = 0;
for (int i = 0; i < 32; i++) {
if (bit[i] % 3 == 1) {
res += (1 << i);
}
}
return res;
}
}
方法二:
有限状态自动机
看傻了也看秃了但还是看不懂😭😭😭,
附链接面试题56 - II. 数组中数字出现的次数 II(位运算 + 有限状态自动机,清晰图解)
class Solution {
public int singleNumber(int[] nums) {
int one = 0, two = 0;
for (int i : nums) {
one = one ^ i & ~two;
two = two ^ i & ~one;
}
return one;
}
}
57. 和为s的两个数字(easy)
【题目】输入一个递增排序的数组和一个数字s
,在数组中查找两个数,使得它们的和正好是s
。如果有多对数字的和等于s
,则输出任意一对即可。
限制:
1 <= nums.length <= 10^5
1 <= nums[i] <= 10^6
【示例】
输入:nums = [2,7,11,15], target = 9
输出:[2,7] 或者 [7,2]
--------------------------------------------
输入:nums = [10,26,30,31,47,60], target = 40
输出:[10,30] 或者 [30,10]
【解题思路】
双指针
class Solution {
public int[] twoSum(int[] nums, int target) {
int le = 0, ri = nums.length - 1;
while (true) {
if (nums[le] + nums[ri] > target) {
ri--;
} else if(nums[le] + nums[ri] < target) {
le++;
} else {
break;
}
}
return new int[]{nums[le], nums[ri]};
}
}
57 - II. 和为s的连续正数序列(easy)
【题目】输入一个正整数 target
,输出所有和为 target
的连续正整数序列(至少含有两个数)。
序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
【示例】
输入:target = 15
输出:[[1,2,3,4,5],[4,5,6],[7,8]]
【解题思路】
滑动窗口
class Solution {
public int[][] findContinuousSequence(int target) {
int sum = 0;
int le = 1, ri = 1;
List<int[]> list = new ArrayList<>();
while (le < (target + 1) / 2) {
if (sum < target) {
sum += ri;
ri++;
} else if (sum > target) {
sum -= le;
le++;
} else {
list.add(new int[]{le, ri});
sum -= le;
le++;
}
}
int[][] res = new int[list.size()][];
for (int i = 0; i < list.size(); i++) {
int[] x = list.get(i);
res[i] = new int[x[1] - x[0]];
for (int j = x[0]; j < x[1]; j++) {
res[i][j - x[0]] = j;
}
}
return res;
}
}
58 - I. 翻转单词顺序(easy)
【题目】输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student. “,则输出"student. a am I”。
说明:
- 无空格字符构成一个单词。
- 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
- 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
【示例】
输入: "the sky is blue"
输出: "blue is sky the"
【解题思路】
class Solution {
public String reverseWords(String s) {
s = s.trim();
String[] strs = s.split(" ");
StringBuffer sb = new StringBuffer();
for (int i = strs.length - 1; i >= 0; i--) {
if (strs[i].equals("")) continue;
sb.append(strs[i] + " ");
}
return sb.toString().trim();
}
}
58 - II. 左旋转字符串(easy)
【题目】字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。
限制:
1 <= k < s.length <= 10000
限制:
1 <= k < s.length <= 10000
【示例】
输入: s = "abcdefg", k = 2
输出: "cdefgab"
【解题思路】
class Solution {
public String reverseLeftWords(String s, int n) {
if (n == 0) return s;
return s.substring(n, s.length()) + s.substring(0, n);
}
}
59 - I. 滑动窗口的最大值(easy)
【题目】给定一个数组 nums
和滑动窗口的大小 k
,请找出所有滑动窗口里的最大值。
提示:
你可以假设 k 总是有效的,在输入数组不为空的情况下,1 ≤ k ≤ 输入数组的大小。
【示例】
输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值
--------------- -----
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
【解题思路】
单调队列
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || k < 1 || nums.length < k) {
return new int[]{};
}
int[] res = new int[nums.length - k + 1];
Deque<Integer> queue = new ArrayDeque<>();
int index = 0;
for (int i = 0; i < nums.length; i++) {
if (!queue.isEmpty() && i - queue.peek() == k) {
queue.poll();
}
while (!queue.isEmpty() && nums[i] > nums[queue.peekLast()]) {
queue.pollLast();
}
queue.offer(i);
if (i >= k - 1) {
res[index++] = nums[queue.peek()];
}
}
return res;
}
}
59 - II. 队列的最大值(medium)
【题目】请定义一个队列并实现函数 max_value
得到队列里的最大值,要求函数max_value
、push_back
和 pop_front
的均摊时间复杂度都是O(1)
。
若队列为空,pop_front
和 max_value
需要返回 -1
限制:
1 <= push_back,pop_front,max_value的总操作数 <= 10000
1 <= value <= 10^5
【示例】
输入:
["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]
【解题思路】
单调队列
class MaxQueue {
Deque<Integer> queue;
Deque<Integer> maxQueue;
public MaxQueue() {
queue = new ArrayDeque<Integer>();
maxQueue = new ArrayDeque<Integer>();
}
public int max_value() {
return queue.isEmpty() ? -1 : maxQueue.peek();
}
public void push_back(int value) {
queue.offer(value);
while (!maxQueue.isEmpty() && maxQueue.peekLast() < value) {
maxQueue.pollLast();
}
maxQueue.offer(value);
}
public int pop_front() {
if (queue.isEmpty()) {
return -1;
}
int val = queue.poll();
if (maxQueue.peek() == val) {
maxQueue.poll();
}
return val;
}
}
60. n个骰子的点数(medium)
【题目】把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。
你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。
【示例】
输入: 1
输出: [0.16667,0.16667,0.16667,0.16667,0.16667,0.16667]
【解题思路】
动态规划
class Solution {
public double[] twoSum(int n) {
double []pre = {1 / 6d, 1 / 6d, 1 / 6d, 1 / 6d, 1 / 6d, 1 / 6d};
for (int i = 2; i <= n; i++) {
double[] cur = new double[5 * i + 1];
for (int j = 0; j < pre.length; j++) {
for (int a = 0; a < 6; a++) {
cur[j + a] += pre[j] * (1 / 6d);
}
}
pre = cur;
}
return pre;
}
}