LeetcodeLearning Day 5

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秋招-农田中作物的最大产量 它们之间存在什么共同之处?体现了什么思想?

​ 两道题目都对可复用的进行打包先存储好,以便后续使用直接使用已计算好的结果,降低时间复杂度。如题中多次用到了数组求和的结果,因此可以提 前计算好并存起来。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值