刷题日记:面试经典 150 题 DAY1
88. 合并两个有序数组
原题链接88. 合并两个有序数组
第一直觉:双指针,从前向后进行遍历。问题是题目要求将合并后的数组存储在第一个数组里,一开始没看见,解决方法是补一步数组复制即可。时间复杂度和额外空间复杂度都是 O ( m + n ) O(m+n) O(m+n)
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
vector<int> result;
int i = 0, j = 0;
while(i < m && j < n) {
if(nums1[i] <= nums2[j]) {
result.push_back(nums1[i]);
i++;
} else {
result.push_back(nums2[j]);
j++;
}
}
while(i < m) {
result.push_back(nums1[i++]);
}
while(j < n) {
result.push_back(nums2[j++]);
}
for(int i = 0;i < m+n;i++){
nums1[i] = result[i];
}
}
};
后面去学习了官方题解给出的空间复杂度为
O
(
1
)
O(1)
O(1)的解法。
直觉上,上面做法完全没有利用到nums1数组后面的部分,所以优化要从如何利用这部分空间开始。整体还是要选择双指针,但是从前向后这个做法行不通。
所以选择从后向前进行遍历即可。这里有一个问题,就是合并后新数组会不会覆盖原nums1的元素。简单的进行判断是不会的:假设我们从后向前已经合并了
(
n
+
s
)
(n+s)
(n+s)个元素(毕竟只有占满后面n个空位才有可能覆盖到原nums1的元素),再假设nums1我们已经遍历了
t
t
t个,nums2数组我们已经遍历了
l
l
l个,由于
n
+
s
=
t
+
l
,
l
≤
n
n+s = t+l,l \leq n
n+s=t+l,l≤n,得
t
≤
s
t \leq s
t≤s,即没有影响
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
int i = m-1, j = n-1;
int index = m+n-1;
while(i >=0 && j >= 0) {
if(nums1[i] >= nums2[j]) {
nums1[index--] = nums1[i--];
} else {
nums1[index--] = nums2[j--];
}
}
while(i >= 0) {
nums1[index--] = nums1[i--];
}
while(j >= 0) {
nums1[index--] = nums2[j--];
}
}
};
27. 移除元素
原题链接:27. 移除元素
考虑双指针,本来想一个放数组头一个放数组尾,这样处理边界条件没处理明白。遂考虑快慢两指针。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slow = 0 , fast = 0;
int size = nums.size();
while(fast < size) {
if(nums[fast] != val) {
nums[slow++] = nums[fast];
}
fast++;
}
return slow;
}
};
26. 删除有序数组中的重复项
原题链接 26. 删除有序数组中的重复项
思路和上一题一模一样
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int slow = 0, fast = 0;
for(;fast < nums.size();slow++) {
nums[slow] = nums[fast];
while(fast < nums.size() && nums[fast] == nums[slow]) {
fast++;
}
}
return slow;
}
};
80. 删除有序数组中的重复项 II
原题链接:80. 删除有序数组中的重复项 II
和上题几乎一模一样,先按照上题的思路写
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int slow = 2, fast = 2;
int len = nums.size();
if(len <= 2) {
return len;
}
for(;fast < len;slow++) {
while(fast < len && nums[fast] == nums[slow-2]) {
fast++;
}
nums[slow] = nums[fast++];
}
return slow;
}
};
未通过[1,1,1]
这样的边界,这是因为fast可能会在循环判断前就超出数组边界,加一句判断即可
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int slow = 2, fast = 2;
int len = nums.size();
if(len <= 2) {
return len;
}
for(;fast < len;slow++) {
while(fast < len && nums[fast] == nums[slow-2]) {
fast++;
}
if(fast >= len) break;
nums[slow] = nums[fast++];
}
return slow;
}
};
感觉不是很优雅,去学习了题解
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int slow = 2, fast = 2;
int len = nums.size();
if(len <= 2) {
return len;
}
for(;fast < len;fast++) {
if(nums[slow-2] != nums[fast]) {
nums[slow++] = nums[fast];
}
}
return slow;
}
};
感觉可能是for循环中判断和自增的东西保持一致比较好
169. 多数元素
原题链接 169. 多数元素
一个很经典的问题,官方题解中给出了较全的做法。有两个方法没那么直觉且有学习价值
- 分治
- Boyer-Moore 投票算法
分治法基于:
- 将整体分成两个部分,整体的众数至少是其中一个部分的多数元素
- 每次对半劈开,递归求解。合并时要判断其中一部分的多数元素是不是整体的多数元素
代码如下:
class Solution {
int count(vector<int>& arr,int x,int h,int l) {
int sum = 0;
for(int i = l;i <= h;i++) {
if(arr[i] == x) {
sum++;
}
}
return sum;
}
int f(vector<int>& nums,int h,int l) {
if(h == l) {
return nums[h];
}
int mid = (h+l)/2;
int m_bound = (h-l+1)/2;
int right_m = f(nums,h,mid+1);
int left_m = f(nums,mid,l);
if(count(nums,left_m,h,l) > m_bound) {
return left_m;
}
if(count(nums,right_m,h,l) > m_bound) {
return right_m;
}
return 0;
}
public:
int majorityElement(vector<int>& nums) {
return f(nums,nums.size()-1,0);
}
};
Boyer-Moore 投票算法基于:在一个集合中,如果同时拿掉两个不同的元素,集合的多数元素不变。(如果两个都不是原多数元素,显然不改变;如果其中一个是,则经过简单的计算也可得知不变)整个算法流程如下
- 初始化一个计数和一个candidate
- 如果当前计数为0,将当前元素赋给candidate
- 如果当前元素和candidate相等,计数加1
- 不等,减一
class Solution {
public:
int majorityElement(vector<int>& nums) {
int count = 0, candidate = nums[0];
for(int num : nums) {
if(count == 0) {
candidate = num;
}
if(candidate == num) {
count++;
} else {
count--;
}
}
return candidate;
}
};