Day 5
LeetCode 1184、公交站间的距离
与Day 4 的【模拟】美团2023秋招-小美走公路基本相同,只不过给的索引从0开始,不需要人为减一了。
LeetCode 27、移除元素
使用双指针:右指针 right 指向当前将要处理的元素,左指针 left 指向下一个将要赋值的位置。
-
如果右指针指向的元素不等于 val,它一定是输出数组的一个元素,我们就将右指针指向的元素复制到左指针位置,然后将左右指针同时右移;
-
如果右指针指向的元素等于 val,它不能在输出数组里,此时左指针不动,右指针右移一位。
整个过程保持不变的性质是:区间 [0,left) 中的元素都不等于 val。当左右指针遍历完输入数组以后,left 的值就是输出数组的长度。
这样的算法在最坏情况下(输入数组中没有元素等于 val),左右指针各遍历了数组一次。
C++
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int n = nums.size();
int left = 0;
for (int right = 0; right < n; right++) {
if (nums[right] != val) {
nums[left] = nums[right];
left++;
}
}
return left;
}
};
Python
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
slow = 0
for fast in range(len(nums)):
if nums[fast] != val:
nums[slow] = nums[fast]
slow += 1
return slow
双指针优化
思路
如果要移除的元素恰好在数组的开头,例如序列 [1,2,3,4,5],当 val 为 1 时,我们需要把每一个元素都左移一位。注意到题目中说:「元素的顺序可以改变」。实际上我们可以直接将最后一个元素 5 移动到序列开头,取代元素 1,得到序列 [5,2,3,4],同样满足题目要求。这个优化在序列中 val 元素的数量较少时非常有效。
实现方面,依然使用双指针,两个指针初始时分别位于数组的首尾,向中间移动遍历该序列。
算法
如果左指针 left 指向的元素等于 val,此时将右指针 right 指向的元素复制到左指针 left 的位置,然后右指针 right 左移一位。如果赋值过来的元素恰好也等于 val,可以继续把右指针 right 指向的元素的值赋值过来(左指针 left 指向的等于 val 的元素的位置继续被覆盖),直到左指针指向的元素的值不等于 val 为止。当左指针 left 和右指针 right 重合的时候,左右指针遍历完数组中所有的元素。
这样的方法两个指针在最坏的情况下合起来只遍历了数组一次。与方法一不同的是,方法二避免了需要保留的元素的重复赋值操作。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int left = 0, right = nums.size();
while (left < right) {
if (nums[left] == val) {
nums[left] = nums[right - 1];
right--;
} else {
left++;
}
}
return left;
}
};
LeetCode289、生命游戏
由于修改了格子的值后,会对后面的判断产生影响,而另外开辟空间的开销较大。因此引入复合状态来表示修改后的格子,使得原地修改成为可能。
python
class Solution(object):
def gameOfLife(self, board):
# 8个方向数组
dx = [-1, 0, 1, -1, 1, -1, 0, 1]
dy = [-1, -1, -1, 0, 0, 1, 1, 1]
# 通过多设置两个状态,来实现原地修改的目的
# 4个不同数字对应4中不同种状态:
# 0: 当前死;1:当前活;2:当前死,下一步活;3:当前活,下一步死
# 遍历每一个cell
for i in range(len(board)):
for j in range(len(board[0])):
# 对于当前位置(i, j)周围的活细胞数目变量,初始化为0
neighborLive = 0
# 考虑当前位置的八个方向的近邻点(x, y)
for k in range(8):
x = i + dx[k]
y = j + dy[k]
# 近邻点(x, y)尚未越界,且是活细胞,更新近邻活细胞数目neighborLive
if ((x >= 0) and (x <= len(board)-1) and (y >= 0) and (y <= len(board[0])-1)):
if (board[x][y] == 1 or board[x][y] == 3):
neighborLive += 1
# 根据当前位置细胞状态和近邻活细胞数目,更新当前位置的下一个状态
if (board[i][j] == 0) and (neighborLive == 3):
board[i][j] = 2 # 死细胞复活,0改2
elif (board[i][j] == 1) and ((neighborLive < 2) or (neighborLive > 3)):
board[i][j] = 3 # 活细胞死亡,1改3
# 再次遍历每一个cell,将2改成1(活细胞),将3改成0(死细胞)
for i in range(len(board)):
for j in range(len(board[0])):
if (board[i][j] == 2): board[i][j] = 1
elif (board[i][j] == 3): board[i][j] = 0
也可以用-1来表示细胞由活变死的过程,在计算邻域活细胞数时,格子的值的绝对值为1的就是当前存活的细胞。
C++
class Solution {
public:
void gameOfLife(vector<vector<int>>& board) {
int neighbor[3] = {0,1,-1};
int rowNum = board.size();
int colNum = board[0].size();
for(int row=0; row<rowNum; row++)
{
for(int col=0; col<colNum; col++)
{
int liveNum = 0;
for(int i=0; i<3; i++)
{
for(int j=0; j<3; j++)
{
if(!(neighbor[i]==0 && neighbor[j]==0))
{
int r = row + neighbor[i];
int c = col + neighbor[j];
if((r<rowNum && r>=0) && (c<colNum && c>=0) && (abs(board[r][c])==1))
{
liveNum++;
}
}
}
}
if((liveNum<2 || liveNum>3) && board[row][col] == 1)
{
board[row][col] = -1;
}
if(liveNum == 3 && board[row][col] == 0)
{
board[row][col] = 2;
}
}
}
for(int row=0; row<rowNum;row++)
{
for(int col=0; col<colNum;col++)
{
if(board[row][col]==2)
{
board[row][col]=1;
}
if(board[row][col]==-1)
{
board[row][col]=0;
}
}
}
}
};
简答
1.请使用一两句话简短地描述每一种排序:
冒泡排序:每一次循环将最大的数放到数组最后,直到整个数组完成排序。
选择排序:每一次循环遍历未排序数组,将未排序数组中最小的放到已排序数组的末尾。
插入排序:假定第一个元素是已排序好的数组,每次循环将未排序数组的元素依次插入到已排序数组中合适的位置。
2.比较两道题目,【模拟】OPPO2023秋招提前批-小欧数组求和 和 【模拟】大疆2023秋招-农田中作物的最大产量 它们之间存在什么共同之处?体现了什么思想?
两道题目都对可复用的进行打包先存储好,以便后续使用直接使用已计算好的结果,降低时间复杂度。如题中多次用到了数组求和的结果,因此可以提 前计算好并存起来。