算法(1)-数据结构-数组、双指针技巧

Abstract

一维数组
// 724.寻找数组的中心下标 left_sum  right_sum 数组
int pivotIndex(vector<int>& nums);
// 1.2 搜索插入位置, 排序数组插入后有序性不变
// 1.3 合并区间
// 1.4 至少是其他数字两倍大的最大数
// 1.5 加一

双指针 -- 快慢指针
// 83.删除排序链表中的重复元素 --right_p != nullptr,先覆盖后移动
ListNode* deleteDuplicates(ListNode* head);
// 26.删除有序数组中的重复项 -- right < n,先移动后覆盖 nums[left]有效,nums[left+1]待填充
int removeDuplicates(vector<int>& nums);
// 27.移除元素(简单)       -- right < n,先覆盖后移动 nums[left-1]有效,nums[left]待填充
int removeElement(vector<int>& nums, int val);
// 283.移动零 -- while 循环跳出后还需要注意left 和 right之间的元素赋值为0
void moveZeroes(vector<int>& nums);
// 长度最小的子数组 [slow, fast]
// 最大连续1的个数 [slow, fast]

双指针 -- 左右指针
// 1.两数之和 - 数组未排序,双重for循环o(n^2),找到break(仅一组答案)
 vector<int> twoSum(vector<int>& nums, int target);
// 167.两数之和 II - 数组已经排序 左右双指针o(n), 下标从1开始
 vector<int> twoSum2(vector<int>& nums, int target)
// 15.三数之和-排序, 两重for + 一重while, 控制复杂度为o(n^2)
vector<vector<int>> threeSum(vector<int>& nums) {
// 344.反转字符串 -- left right 原地操作
void reverseString(vector<char>& s);
// 647.回文子串(数) -- 中心扩展法(左右指针)/aux数组(暴力枚举)
int countSubstrings(string s);
// 5.最长回文子串 -- 中心扩展法(左右指针)/aux数组(暴力枚举)
string longestPalindrome(string s);

前缀和 pre_sum[i][j] 累加到[i-1][j-1]的前缀和
// 303. 区域和 检索
class NumArray; int sumRange(int left, int right);
// 304. 二维区域和检索
class NumMatrix; iint sumRegion(int row1, int col1, int row2, int col2);

差分数组
// 370. 区间加法 🔒
// 598. 区间加法 II -- 求最大数的量,维护所有操作的交集,不用二维差分数组
 int maxCount(int m, int n, vector<vector<int>>& ops)// 1109 航班预订 -- 差分数组,由各个航班预订列表统计各个航班的预订总数。
vector<int> corpFlightBookings(vector<vector<int>>& books, int n)
// 1094.拼车 -- 车上的人只会待到 to - 1站, diff[to] -= n, not diff[to+1] -=n 
bool carPooling(vector<vector<int>>& trips, int capacity);

在一般的数组题中,只采用一个指针来进行迭代(from begin to end);有些时候需要用两个指针来迭代数组。

两类双针:

  1. 两个指针从不同的位置出发(速度相同):两端向中间、起点不同方向相同 【left right】典型题:翻转字符串(两头往中间),回文子串(中心扩展)
  2. 两个指针以不同的速度移动:一个指针快一些,另一个指针慢一些 【slow, fast】典型题:原地操作, 去重,去值,去零

Introduction

集合: 一个/多个确定的元素构成的整体。一般种类相同,当然也可以不同。集合中的元素没有顺序。很多编程语言中拥有一些数据结构,是在集合的概念中增加了一些规则而来的。无序。

列表:(又称线性列表)一种数据项构成的有限的序列。即按一定顺序排列的数据项的集合。有序,长度可变。列表常见的表现形式有:数组,链表,堆栈,队列。

–> 数组: 是一种最基本的数据结构,用于按顺序存储数据。数组中元素通过索引来实现读写操作。数组有一维数组,也有多维数组。一般编程语言中,数组具有固定容量,需要在初始化的时候确定数组大小,后续无法修改其长度。操作不是十分便利,于是大多编程语言提供了动态数组的数据结构,其大小可变。在Python中就没这么复杂,一个list数据结构就可以灵活满足众多需求。

数组基本操作:增、删、查、改
读:通过访问索引的方式来读取的,索引一般从 0 开始。访问的时间复杂度为o(1)。

1. 一维数组

1.1 寻找数组的中心索引

724.寻找数组的中心下标 – 给定一个整数类型的数组,编程实现返回数组的中心索引。中心索引的定义:中心索引左侧所有元素的和大于右侧所有元素的和。特殊情况:如果中心索引不存在,返回-1;如果存在多个中心所以,返回最左边的一个

定义两个数组:
l e f t _ s u m [ i ] = ∑ j = 0 i − 1 n u m s [ j ] ,    i = 1 , 2 , 3 , . . . , n − 1 left\_sum[i]=\sum_{j=0}^{i-1}nums[j],\ \ i=1,2,3,...,n-1 left_sum[i]=j=0i1nums[j],  i=1,2,3,...,n1

r i g h t _ s u m [ i ] = ∑ j = i + 1 n − 1 n u m s [ j ] ,    i = n − 2 , n − 3 , . . . , 0 right\_sum[i]=\sum_{j=i+1}^{n-1}nums[j], \ \ i=n-2,n-3,...,0 right_sum[i]=j=i+1n1nums[j],  i=n2,n3,...,0

逐个比较left_sum与right_sum的元素,如果相同则返回对应索引,如果遍历完也没有相同的,则返回-1

例子:nums = [1, 7, 3, 6, 5, 6]
left_sum = [0,1,8,11,17,22]
right_sum = [27,20,17,11,6,0]

class Solution(object):
    def pivotIndex(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        res = -1
        n = len(nums)
        left_sum = [0] * n
        right_sum  = [0] * n
        for i in range(1, n):
            left_sum[i] = left_sum[i-1] + nums[i-1]
        for i in range(n-2, -1, -1):
            right_sum[i] = right_sum[i+1] + nums[i+1]
        for i in range(n):
            if left_sum[i] == right_sum[i]:
                res = i
                break
        return res 
class Solution {
public:
    int pivotIndex(vector<int>& nums) {
        int n = nums.size();
        vector<int> left_sum(n, 0);
        vector<int> right_sum(n, 0);
        for (int i = 1; i < n; i++) {
            left_sum[i] = left_sum[i-1] + nums[i-1];
            int j = n - 1 - i;
            right_sum[j] = right_sum[j + 1] + nums[j + 1];
        }
        for (int i = 0; i < n; i++) {
            if (left_sum[i] == right_sum[i]) {
                return i;
            }
        }
        return -1;
    }
};

1.2 搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。
二分查找能够实现 O(log n) 的时间复杂度。

class Solution(object):
    def searchInsert(self, nums, target):
        left_idx, right_idx = 0, len(nums)-1
        while(left_idx <= right_idx):
            # print(left_idx, right_idx)
            mid = int(left_idx + right_idx) / 2
            if nums[mid] == target:
                return mid
            elif nums[mid] < target:
                left_idx = mid + 1
            elif nums[mid] > target:
                right_idx = mid - 1
        return left_idx

1.3 合并区间

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
思路:先排序,然后再逐个合并就好了。

class Solution(object):
    def merge(self, intervals):
        intervals.sort(key = lambda x:x[0])
        res = []
        for interval in intervals:
            if res == [] or res[-1][1] < interval[0]:
                res.append(interval)
            else:
                res[-1][1] = max(res[-1][1], interval[1])
        return res

1.4 至少是其他数字两倍大的最大数

在一个给定的数组nums中,总是存在一个最大元素 。
查找数组中的最大元素是否至少是数组中每个其他数字的两倍。
如果是,则返回最大元素的索引,否则返回-1。

思路:
一次搜索,找最大值
二次搜索,判断是否符合两倍条件

class Solution(object):
    def dominantIndex(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        n=len(nums)
        max_num=float("-INF")
        max_ind=-1
        for i in range(n):
            if nums[i]>max_num:
                max_num=nums[i]
                max_ind=i

        for i in range(n):
            if i ==max_ind:
                continue
            # 判断是否是nums[i]的两倍
            if max_num<nums[i]*2:
                
                return -1
        return max_ind
            

1.5 加一

给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。
最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。
你可以假设除了整数 0 之外,这个整数不会以零开头。

核心难点:有进位的情况该如何处理,因为是加一操作,大大简化了进位难度,因为只有可能产生的进位是1

思路1在这里插入代码片:直接在数组上操作,判断数组每位置数字是否产生进位:

class Solution(object):
    def plusOne(self, digits):
        """
        :type digits: List[int]
        :rtype: List[int]
        """
        n=len(digits)
        digits[n-1]+=1
        # 判断进位,如何存储进位信息。
        flag=0
        for i in range(n-1,-1,-1):
            if flag==1:
                digits[i]+=1
                flag=0
            if digits[i]==10:
                digits[i]=0
                flag=1
        if flag!=0:
            res=[flag]
            for i in range(n):
                res.append(digits[i])
            return res
        return digits
            

思路2:数组转数字,数字+1处理,再转数组。

    def plusOne(self, digits):
        """
        :type digits: List[int]
        :rtype: List[int]
        """
        # 整数转数字,加一之后转会数组
        n=len(digits)
        nums=0
        e=0
        for i in range(n-1,-1,-1):
            nums+=digits[i]*10**e
            e+=1
        nums+=1
        res=[]
        while(nums>0):
            digit=nums%10
            res.append(digit)
            nums=(nums-digit)/10
        res.reverse()
        return res

2. 一维数组双指针

在一般的数组题中,只采用一个指针来进行迭代(from begin to end);有些时候需要用两个指针来迭代数组。

两类双针:

  1. 两个指针从不同的位置出发(速度相同):两端向中间、起点不同方向相同 【left right】
  2. 两个指针以不同的速度移动:一个指针快一些,另一个指针慢一些 【slow, fast】

快慢双指针

2.1 移动元素–覆盖去重、移除元素、移动零 [slow, fast]

26.删除有序数组中的重复项 – 给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。

27.移除元素 – 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

83.删除排序链表中的重复元素 – 给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。(类似题型)

283.移动零 – 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。请注意 ,必须在不复制数组的情况下原地对数组进行操作。

class Solution {
public:
	// 26. 删除有序数组中的重复项
    int removeDuplicates(vector<int>& nums) {
        int right = 0;
        int left = 0;
        int n = nums.size();
        while(right < n) {
            if (nums[right] != nums[left]) {
                left++;                 // nums[left]有效,nums[left+1]待填充
                nums[left] = nums[right];
            } 
            right++;
        }
        return left+1;
    }
    
    // 27.移除元素 
    int removeElement(vector<int>& nums, int val) {
        int n = nums.size();
        int left = 0;
        int right = 0;
        while(right < n) {
            if (nums[right] != val) {
                nums[left] = nums[right];  // nums[left] 待填充,nums[left-1]有效
                left++;
            } 
            right++;
        }
        return left;
    }
  
 	// 83. 删除排序链表中的重复元素 
    ListNode* deleteDuplicates(ListNode* head) {
        ListNode* dummy = new ListNode(-101, head);
        ListNode* right_p = dummy;
        ListNode* left_p = dummy;
        while(right_p != nullptr) {
            if (right_p->val != left_p->val) {
                left_p->next = right_p; // 
                left_p = left_p->next;
            }
            right_p = right_p->next;
        }
        left_p->next = nullptr;  // 截断后续
        return dummy->next;
    }
    // 283.移动零
    void moveZeroes(vector<int>& nums) {
        int n = nums.size();
        int rihgt = 0;
        int left = 0;
        while (rihgt < n) {
            if (nums[rihgt] != 0) {
                nums[left] = nums[rihgt];
                left++;
            }
            rihgt++;
        }
        while(left < rihgt) {
            nums[left] = 0;
            left++;
        }
    }
};

2.2 最大连续1的个数 [slow, fast]

给定一个二进制数组,计算其中最大连续1的个数。
k,m=0,0
快指针遇到1一直加,直至遇到0,统计一下当前1的个数,更新慢指针的位置。
慢指针指向连续1的开头,快指针指向连续1的结尾。
当快指针遇到0之后,统计一下结果。当遇到下一个1时慢指针才更新。

class Solution(object):
    def findMaxConsecutiveOnes(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        n=len(nums)
        count=0
        max_count=0
        for i in range(n):
            if nums[i]==1:
                count+=1
            if nums[i]==0:
                max_count=max(max_count,count)
                count=0
        return max(max_count,count)               

2.3 长度最小的子数组 [slow, fast]

给定一个含有n个正整数的数组和一个正整数s,找出该u数组中满足其和》=s的长度最小的子数组,并返回其长度。如果不存在符合条件的连续子数组,返回0.

子数组是一个连续的序列
解法1-暴力法

class Solution(object):
    def minSubArrayLen(self, s, nums):
        """
        :type s: int
        :type nums: List[int]
        :rtype: int
        """
        n=len(nums)
        res=n+1
        for i in range(n):
            temp=0
            for j in range(i,n):
                temp+=nums[j]
                if temp>=s:
                    res=min(res,j-i+1)
                    break
        #print (res)
        if res==n+1:
            return 0
        else:
            return res

解法2-双指针
以上思路是保持子数组的左端点不动,去寻找右端点。其实一旦知道这个位置开始的子数组不是最优答案,我们就可以移动左端点。使用两个指针,一个指向数组的开始位置,一个指向数组的最后位置。维护区间内的和>=s,且长度最小。

class Solution(object):
    def minSubArrayLen(self, s, nums):
        """
        :type s: int
        :type nums: List[int]
        :rtype: int
        """
        n=len(nums)
        res=n+1
        temp_sum=0
        left=0
        for i in range(n):
            temp_sum+=nums[i]
            while(temp_sum>=s):
                res=min(res,i-left+1)
                temp_sum-=nums[left]
                left+=1
        #print (res)
        if res==n+1:
            return 0
        else:
            return res

左右双指针

2.4 反转字符串 [left, right]

344.反转字符串 – 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。要求原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

example:
输入:[“h”,“e”,“l”,“l”,“o”]
输出:[“o”,“l”,“l”,“e”,“h”]

思路:利用两个指针来完成迭代,一个指针从第一个元素开始,另一个指针从末尾开始,持续交换他们所指向的元素,直至俩个指针相遇)

class Solution(object):
    def reverseString(self, s):
        """
        """
        i=0
        j=len(s)-1
        while(i<j):
            s[i],s[j]=s[j],s[i]
            i+=1
            j-=1
        return s
class Solution {
public:
    void reverseString(vector<char>& s) {
        int n = s.size();
        int left = 0, right = n - 1;
        while (left < right) {
            char tmp_s = s[left];
            s[left] = s[right];
            s[right] = tmp_s;
            left++;
            right--;
        }
    }
};

2.5 数组拆分 [left, right]

给定长度为2n的数组,将这些数字分为n对,例如 ( a 1 , b 1 ) , ( a 2 , b 2 ) , . . . , ( a n , b n ) (a_1,b_1),(a_2,b_2),...,(a_n,b_n) (a1,b1),(a2,b2),...,(an,bn),使得从1-n的 m i n ( a i , b i ) min(a_i,b_i) min(ai,bi)总和最大。

举例: a 1 , a 2 , a 3 , a 4 , a 5 , a 6 a_1,a_2,a_3,a_4,a_5,a_6 a1,a2,a3,a4,a5,a6升序排列
6个数分三对,能出现在和计算式中的最大数为 a 5 a_5 a5(此时 a 5 和 a 6 a_5和a_6 a5a6配对),同理剩下的4个数中,依次出现在和式中的为 a 1 , a 3 a_1,a_3 a1,a3

解题思路:先对数组排序,然后依次叠加偶数索引元素。

class Solution(object):
    def arrayPairSum(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        n=len(nums)
        res=0
        # nums.sort()
        for i in range(n):
            if i%2==0:
                res+=nums[i]
        return res
            

2.6 两数之和2 [left, right]

167.两数之和 II – 输入有序数组 --给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列,请你从数组中找出满足相加之和等于目标数 target 的两个数。(假设每个输只对应一组下标 ,数组元素不重复使用组和。只使用常量级的额外空间。

【Note-数组已经排序 左右双指针,时间复杂度o(n), 下标从1开始】

class Solution {
public:
    // 167.两数之和 II - 数组已经排序 左右双指针o(n), 下标从1开始
    vector<int> twoSum2(vector<int>& nums, int target) {
        int n = nums.size();
        int left = 0, right = n - 1;
        vector<int> res(2, -1);
        while(left < right) {
            if (nums[left] + nums[right] == target) {
                res[0] = left + 1;
                res[1] = right + 1;
                return res;
            }else if (nums[left] + nums[right] < target) {
                left++;
            } else {
                right--;
            }
        }
        return res;
    }
}

2.7 回文子串(数),最长回文子串(长)

647.回文子串(数) – 给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。
回文字符串 是正着读和倒过来读一样的字符串。子字符串是字符串中的由连续字符组成的一个序列。具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
5.最长回文子串(串) – 给你一个字符串 s,找到 s 中最长的回文子串。如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。

class Solution {
public:
	// 647.回文子串(数)-解1: 遍历所有子串,判断是否为回文子串,借助axu数组利用空间换时间
    int countSubstrings(string s) {
        int n = s.size();
        int res = 0;
        vector<vector<bool>> aux(n, vector<bool>(n, false));
        for (int i = 0; i < n; i++) {
            aux[i][i] = true;
            res++;
        }
        for (int i = n -2; i > -1; i--) {
            for (int j = i + 1; j < n; j++) {
                if (j == i + 1) {
                    aux[i][j] = (s[i] == s[j]);
                } else {
                     aux[i][j] = (s[i] == s[j] && aux[i+1][j-1]);
                }
                if (aux[i][j]) {
                    res++;
                }
            }
        }
        return res;
    }
    // 647.回文子串(数)-解2:真正中心扩展法,以 单/双字符为中心往两端扩展,每次扩展更新计数器,遇到非回文扩展即停止扩展
    //     解1 aux数组解法的剪枝版本
    int countSubstrings2(string s) {
        int n = s.size();
        int res = 0;
        for (int i = 0; i < n; i++) {
            res += palidrome_num(s, i, i);
            res += palidrome_num(s, i, i+1);
        }
        return res;
    }
    
    int palidrome_num(string s, int left, int right) {
        int n = s.size();
        int res = 0;
        while(left > -1 && right < n && s[left] == s[right]) {
            res++;
            left--;
            right++;
        }
        return res;
    }
	// 5.最长回文子串-解1: aux解
    string longestPalindrome(string s) {
        int n = s.size();
        vector<vector<bool>> aux(n, vector<bool>(n, false));
        string res;
        for (int i = 0; i < n; i ++) {
            aux[i][i] = true;
            res = s[0];
        }
        for (int i = n - 2; i > -1; i--) {
            for (int j = i + 1; j < n; j++) {
                if (j == i + 1) {
                    aux[i][j] =  (s[i] == s[j]);
                } else {
                    aux[i][j] = (s[i] == s[j] && aux[i+1][j-1]); // caution 已有条件使用
                }
                if (aux[i][j] && (res.size() < j - i + 1)) {
                    res = s.substr(i, j - i + 1);
                }
            }
        }
        return res;
    }
    
	// 5.最长回文子串-解2: 中心扩展法 
    string longestPalindrome(string s) {
        int n = s.size();
        string res;
        if (n > 0) {
            res = s[0];
        }
        for (int i = 0; i < n; i++) {
            int left = i, right = i;
            string s1 = palindrome(s, i, i);
            string s2 = palindrome(s, i, i+1);
            res = s1.size() > res.size() ? s1 : res;
            res = s2.size() > res.size() ? s2 : res; 
        }
        return res;
    }

    string palindrome(string s, int left, int right) {
        int n = s.size();
        while (left > -1 && right < n && s[left] == s[right]) {
            left--;
            right++;
        }
        return s.substr(left+1, (right - 1) - (left+ + 1) + 1); // 返回字符串,有效的left 和 right 要各自相中心缩小一格
    }


};


3. 二维数组

二维数组是一种结构较为特殊的数组,只是将数组中的每个元素变成了一维数组。所以二维数组的本质上仍然是一个一维数组,内部的一维数组仍然从索引 0 开始,可以将二维数组看作一个矩阵,并处理矩阵的相关问题。

数组在内存上连续存储。

3.1 旋转矩阵

将 N × N 矩阵顺时针旋转90度。
1 2 3 4 5 6 7 8 9 (1) \begin{matrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \end{matrix} \tag{1} 147258369(1)

解题思路:
1.旋转90度后,(i, j)位置上的数字会换到(j, N-1-i)
2.每4个数字可以形成个循环圈,4 * 90 = 360:1会到3的位置,3会到9的位置,9会到7的位置,7会到1的位置。即这四个相互置换数字坐标满足:
{ M [ j , N − 1 − i ] ← M [ i , j ] M [ N − 1 − i , N − 1 − j ] ← M [ j , N − 1 − i ] M [ N − 1 − j , i ] ← M [ N − 1 − i , N − i − j ] M [ i , j ] ← M [ N − 1 − j , i ] \left\{ \begin{aligned} M[j, N-1-i] & \leftarrow M[i, j]\\ M[N-1-i, N-1-j] & \leftarrow M[j, N-1-i] & \\ M[N-1-j, i] & \leftarrow M[N-1-i, N-i-j] \\ M[i, j] & \leftarrow M[N-1-j, i] \\ \end{aligned} \right. M[j,N1i]M[N1i,N1j]M[N1j,i]M[i,j]M[i,j]M[j,N1i]M[N1i,Nij]M[N1j,i]
3.仅需要旋转关键位置的数字即可:当 N为偶数时,我们需要枚举 n 2 / 4 = ( n / 2 ) × ( n / 2 ) n^2/4=(n/2)×(n/2) n2/4=(n/2)×(n/2) 个位置,可以将该图形分为四块,当 n为奇数时,由于中心的位置经过旋转后位置不变,我们需要
枚举 ( n 2 − 1 ) / 4 = ( ( n − 1 ) / 2 ) × ( ( n + 1 ) / 2 ) (n^2−1)/4=((n−1)/2)×((n+1)/2) (n21)/4=((n1)/2)×((n+1)/2)个位置.

class Solution(object):
    def rotate(self, matrix):
        N = len(matrix)
        for i in range(N//2):
            for j in range((N+1)//2):
                matrix[i][j], matrix[j][N-1-i], matrix[N-1-i][N-1-j], matrix[N-1-j][i]\
                = matrix[N-1-j][i], matrix[i][j], matrix[j][N-1-i], matrix[N-1-i][N-1-j]
        return matrix

3.2 零矩阵

编写一种算法,若M × N矩阵中某个元素为0,则将其所在的行与列清零。

记录某行某列存在0即可,某行某列存在多个0, 也只是被删除一次。

class Solution(object):
    def setZeroes(self, matrix):
        """
        :type matrix: List[List[int]]
        :rtype: None Do not return anything, modify matrix in-place instead.
        """
        delet_x, delet_y = [], []
        for i in range(len(matrix)):
            for j in range(len(matrix[0])):
                if matrix[i][j] == 0:
                    delet_x.append(i)
                    delet_y.append(j)
        for i in delet_x:
            for j in range(len(matrix[0])):
                matrix[i][j] = 0
        for j in delet_y:
            for i in range(len(matrix)):
                matrix[i][j] = 0 

class Solution(object):
    def setZeroes(self, matrix):
        """
        :type matrix: List[List[int]]
        :rtype: None Do not return anything, modify matrix in-place instead.
        """
        n, m = len(matrix), len(matrix[0])
        delet_x, delet_y = [0] * n, [0] * m
        for i in range(n):
            for j in range(m):
                if matrix[i][j] == 0:
                    delet_x[i] = 1
                    delet_y[j] = 1
        for i in range(n):
            for j in range(m):
                if delet_x[i] == 1 or delet_y[j] == 1:
                    matrix[i][j] = 0
            

3.3 对角线遍历

给你一个大小为 m x n 的矩阵 mat ,请以对角线遍历的顺序,用一个数组返回这个矩阵中的所有元素。
法一:直接模拟
思路与算法

根据题目要求,矩阵按照对角线进行遍历。设矩阵的行数为 mmm, 矩阵的列数为 nnn, 我们仔细观察对角线遍历的规律可以得到如下信息:

一共有 m+n−1m + n - 1m+n−1 条对角线,相邻的对角线的遍历方向不同,当前遍历方向为从左下到右上,则紧挨着的下一条对角线遍历方向为从右上到左下;

设对角线从上到下的编号为 i∈[0,m+n−2]i \in [0, m + n - 2]i∈[0,m+n−2]:

当 iii 为偶数时,则第 iii 条对角线的走向是从下往上遍历;
当 iii 为奇数时,则第 iii 条对角线的走向是从上往下遍历;
当第 iii 条对角线从下往上遍历时,每次行索引减 111,列索引加 111,直到矩阵的边缘为止:

当 i<mi < mi<m 时,则此时对角线遍历的起点位置为 (i,0)(i,0)(i,0);
当 i≥mi \ge mi≥m 时,则此时对角线遍历的起点位置为 (m−1,i−m+1)(m - 1, i - m + 1)(m−1,i−m+1);
当第 iii 条对角线从上往下遍历时,每次行索引加 111,列索引减 111,直到矩阵的边缘为止:

当 i<ni < ni<n 时,则此时对角线遍历的起点位置为 (0,i)(0, i)(0,i);
当 i≥ni \ge ni≥n 时,则此时对角线遍历的起点位置为 (i−n+1,n−1)(i - n + 1, n - 1)(i−n+1,n−1);
根据以上观察得出的结论,我们直接模拟遍历所有的对角线即可。

class Solution(object):
    def findDiagonalOrder(self, mat):
        """
        :type mat: List[List[int]]
        :rtype: List[int]
        """
        m, n = len(mat), len(mat[0])
        res = []
        for i in range(m + n - 1):
            if i % 2 == 1:
                # 由上向下
                x = 0 if i < n else i - n + 1
                y = i if i < n else n - 1
                while (x < m  and y >= 0 ):
                    res.append(mat[x][y])
                    x += 1
                    y -= 1
            else:
                 # 由下向上
                x = i if i < m else m - 1
                y = 0 if i < m else i - m + 1
                while (x >= 0 and y < n):
                    res.append(mat[x][y])
                    x -= 1
                    y += 1
        return res

3.4 杨辉三角

第i行的元素个数为i+1个,i的取值范围为0,1,…,numRows-1
第i行元素的第一个元素(索引为0)和最后一个元素(索引为i),该行中其它元素的索引j取值范围为0,1,…,i-1.

class Solution(object):
    def generate(self, c):
        """
        :type numRows: int
        :rtype: List[List[int]]
        """
        res=[]
        for i in range(numRows):
            if i==0:
                res.append([1])
            elif i==1:
                res.append([1,1])
            else:
                res.append([1]*(i+1))    
                for j in range(1,i):
                    res[i][j]=res[i-1][j-1]+res[i-1][j]
        return res            

4. 数组典型题

1.数组的常用排序算法及其时间复杂度分析十分重要
2.二分查找是一种十分重要的技术,用于在排序数组中搜索特i定元素
3.灵活应用双指针技巧十分重要,双指针技可以应用于:链表中快慢指针问题,滑动窗口问题

4.1 旋转数组

给定一个数组,将数组中的元素向右移动K个位置,其中k为非负数。
空间复杂度:o(1)
思路1:三次求逆:先整体求逆,然后nums[0,k]求逆,然后再nums[k+1:]求逆

class Solution(object):
    def rotate(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: None Do not return anything, modify nums in-place instead.
        """
        def rever_nums(nums,l,r):  # 原地置换,用index操作,nums[:flag]只是一个备份,没有修改元数组
            while(l<r):
                nums[l],nums[r]=nums[r],nums[l]
                l+=1
                r-=1
        n=len(nums)
        flag=k%n
        rever_nums(nums,0,n-1)
        rever_nums(nums,0,flag-1)
        rever_nums(nums,flag,n-1)
        return nums  

思路2:计算每个数组移动到的目标位置,将目标位置的信息记录下来作为下一个操作的对象(30/35)—[-1,-100,3,99]2测试用例没过.

class Solution(object):
    def rotate(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: None Do not return anything, modify nums in-place instead.
        """
        n=len(nums)
        action=0
        index=0
        current_val=nums[0]
        while(action<=n):
            target_index=(index+k)%n
            target_val=nums[target_index]
            nums[target_index]=current_val
            action+=1
            index=target_index
            current_val=target_val
        return nums    

修正思路:环装替换法

参考官网解题思路:https://leetcode-cn.com/problems/rotate-array/solution/xuan-zhuan-shu-zu-by-leetcode/

思路3,暴力法,每次移动一个,移动k次
(34/35)时间超出限制

class Solution(object):
    def rotate(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: None Do not return anything, modify nums in-place instead.
        """
        n=len(nums)
        flag=k%n
        for i in range(flag):
            current=nums[n-1]
            for j in range(n):  # j:[0,n-1]
                temp=nums[j]
                nums[j]=current
                current=temp
        return nums

4.2 杨辉三角2

返回杨辉三角的第k行,算法空间复杂度,o(k)

思路1:计算整个杨辉三角,返回最后一行

class Solution(object):
    def getRow(self, rowIndex):
        res=[]
        for i in range(rowIndex+1): # i:[0,rowIndex]  rowIndex+1行
            if i==0:
                res.append([1])
            elif i==1:
                res.append([1,1])
            else:
                res.append([1]*(i+1))
                for j in range(1,i):
                    res[i][j]=res[i-1][j-1]+res[i-1][j]
        return res[-1]

思路2:空间复杂度优化,只有上一行计算下一行:

class Solution(object):
    def getRow(self, rowIndex):

        if rowIndex==0:
            return [1]
        if rowIndex==1:
            return [1,1]
        pre=[1,1]
        for i in range(2,rowIndex+1):
            cur=[1]*(i+1)
            for j in range(1,i):
                cur[j]=pre[j-1]+pre[j]
            pre=cur
        return cur

4.3 反转字符串中的单词

151.反转字符串中的单词 – 给定一个字符串,逐个翻转字符串中的每个单词。单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。

输入:s = “the sky is blue”
输出:“blue is sky the”

557.反转字符串中的单词 III – 给定一个字符串 s ,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。

输入:s = “Let’s take LeetCode contest”
输出:“s’teL ekat edoCteeL tsetnoc”

class Solution(object):
    def reverseWords(self, s):
        """
        :type s: str
        :rtype: str
        151 解1:要识别字符串中的每个单词,然后调整顺序。从最后开始遍历,识别一个单词后,往结果中添加。
        """
        res =""
        n=len(s)
        flag=0     # 判断是否开始识别一个单词
        for i in range(n-1,-1,-1):
            #print(i,s[i],flag)
            if s[i]!=" " and flag==0: # 一个单词的起始
                flag=1
                right=i
                l_word=1
            elif s[i]!=" " and flag==1: # 单词左下标往前走
                l_word+=1
            elif s[i]==" " and flag==1: # 单词左下标走到了边界
                word=s[right-l_word+1:right+1]
                flag=0
                res+=word
                res+=" "
            elif s[i]==" " and flag==0:  # 多个空格的时候
                continue
        if flag==1:                      #最后还需要判断是否还有一个单词
            word=s[right-l_word+1:right+1]
            flag=0
            res+=word
        len_res=len(res)
        if len_res==0:
            return ""
        if res[-1]==" ":
            res=res[0:-1]
        return res

    def reverseWords(self, s):
        """
        :type s: str
        :rtype: str
        151 解2:借助内置的函数
			split() 将字符串按空格分成字符串数组
        	reverse() 将字符串数组翻转
        	join 将字符串数组拼接鞥一个字符串。
        """
        s_arr=s.split()
        s_arr.reverse()
        res=" ".join(s_arr)
        return res

    def reverseWords(self, s):
        """
        :type s: str
        :rtype: str
		557解:识别一个单词,送去翻转,添加至结果后
        """
        def rever_word(word):
            n=len(word)
            re_word=""
            for i in range(n-1,-1,-1):
                re_word+=word[i]
            #print(re_word)
            return re_word
        n=len(s)
        l,r=0,0
        res=""
        while(r<n):
            if s[r]!=" ":
                r+=1
            elif s[r]==" ":
                word=s[l:r]
                res+=rever_word(word)
                res+=" "
                l=r+1
                r+=1
        word=s[l:r+1]
        res+=rever_word(word)
        return res   

4.4 前缀和

特点:原始数组不会被修改,频繁查询某个区间累加和。
技巧:构建 pre_sum[i] = pre_sum[i-1] + nums[i-1];

303.区域和检索 - 数组不可变 – 给定一个整数数组 nums,处理以下类型的多个查询:

  1. 计算索引 left 和 right (包含 left 和 right)之间的 nums 元素的 和 ,其中 left <= right
  2. 实现 NumArray 类:
    NumArray(int[] nums) 使用数组 nums 初始化对象
    int sumRange(int i, int j) 返回数组 nums 中索引 left 和 right 之间的元素的 总和 ,包含 left 和 right 两点(也就是 nums[left] + nums[left + 1] + … + nums[right] )

304.二维区域和检索

// 303.区域和检索 - 数组不可变 
class NumArray {
    vector<int> pre_sum;
public:
    NumArray(vector<int>& nums) {
        int n = nums.size();
        pre_sum.resize(n + 1);   
        // pre_sum[i] = sum(nums[0], nums[1], ..., nums[i-1])
        for (int i = 1; i < n + 1; i++) {
            pre_sum[i] = pre_sum[i-1] + nums[i-1];
        }
    }
    int sumRange(int left, int right) {
        // sum(nums[left], nums[left + 1], nums[right])
        return pre_sum[right+1] - pre_sum[left];
    }
};

// 304.二维区域和检索 -- 护所有操作的交集
class NumMatrix {
    vector<vector<int>> pre_sum;

public:
    NumMatrix(vector<vector<int>>& matrix) {
        // 1 <= n, m <= 200
        int n = matrix.size(), m = matrix[0].size();
        pre_sum.resize(n + 1);
        for (int i = 0 ; i < n + 1; i++) {
            pre_sum[i].resize(m + 1);
        }
        for (int i = 1; i < n + 1; i++) {
            for (int j = 1; j < m + 1; j++) {
                pre_sum[i][j] = pre_sum[i][j-1] + pre_sum[i-1][j] - pre_sum[i-1][j-1] + matrix[i-1][j-1];
            }
        }
    }

    int sumRegion(int row1, int col1, int row2, int col2) {
        return pre_sum[row2+1][col2+1] + pre_sum[row1][col1] - pre_sum[row2+1][col1] - pre_sum[row1][col2+1];
    }
};

4.5 差分数组

特点:频繁对数组的某个区间元素进行增减,一通操作之后问数组变成什么样子了。
技巧:

  1. 构建 diff[i] = nums[i] - nums[i-1], 通过diff 数组可以反推原始数组,在diff 的i位置+k, 反推原始数组时,后续位置均会+k,
  2. 对matrix[i, j] 均+k的操作,在diff上留下的印记:diff[i] +=k, diff[j+1] - =k (当j+1==n 时无需操作)

370.区间加法 🔒

598.区间加法 II – 给你一个 m x n 的矩阵 M 和一个操作数组 op 。矩阵初始化时所有的单元格都为 0 。ops[i] = [ai, bi] 意味着当所有的 0 <= x < ai 和 0 <= y < bi 时, M[x][y] 应该加 1。
在 执行完所有操作后 ,计算并返回 矩阵中最大整数的个数 。

109.航班预订 – 这里有 n 个航班,它们分别从 1 到 n 进行编号。 有一份航班预订表 bookings ,表中第 i 条预订记录 bookings[i] = [firsti, lasti, seatsi] 意味着在从 firsti 到 lasti (包含 firsti 和 lasti )的 每个航班 上预订了 seatsi 个座位。请你返回一个长度为 n 的数组 answer,里面的元素是每个航班预定的座位总数。

1094.拼车 – 车上最初有 capacity 个空座位。车 只能 向一个方向行驶(也就是说,不允许掉头或改变方向)给定整数 capacity 和一个数组 trips , trip[i] = [numPassengersi, fromi, toi] 表示第 i 次旅行有 numPassengersi 乘客,接他们和放他们的位置分别是 fromi 和 toi 。这些位置是从汽车的初始位置向东的公里数。当且仅当你可以在所有给定的行程中接送所有乘客时,返回 true,否则请返回 false。

class Solution {
public:
	//  598. 区间加法 II -- 求最大数的量,维护所有操作的交集,不用二维差分数组
    int maxCount(int m, int n, vector<vector<int>>& ops) {
        int min_row = m, min_col = n;
        for (vector<int> op : ops) {
            int i = op[0], j = op[1];
            min_row = min(i, min_row);
            min_col = min(j, min_col);
        }
        return min_row * min_col;
    }
    
    // 1109 航班预订 -- 差分数组,由各个航班预订列表统计各个航班的预订总数。
    vector<int> corpFlightBookings(vector<vector<int>>& books, int n) {
        vector<int> res(n, 0);
        vector<int> diff(n, 0);
        // 当k+1==n 时无需操作
        for (vector<int>& book : books) {
            int i = book[0], j = book[1], seats = book[2];
            diff[i-1] += seats;
            if (j < n) {
                diff[j] -= seats;
            }
        }
        res[0] = diff[0];
        for (int i = 1; i < n; i++) {
            res[i] = res[i - 1] + diff[i];
        }
        return res;
    }

   // 1094.拼车 -- 车上的人只会待到 to - 1站, diff[to] -= n, not diff[to+1] -=n 
    bool carPooling(vector<vector<int>>& trips, int capacity) {
        vector<int> diff_vec(1000, 0);
        for (vector<int>& trip : trips) {
            int n = trip[0], from = trip[1], to = trip[2];
            diff_vec[from] += n;
            if (to < 1000) {
                // diff_vec[to+1] -= n;
                diff_vec[to] -= n; // 该站下车,所以人数得减少
            }
        }
        int cur_n = diff_vec[0];  // 第 0 km也可以上人
        for (int i = 1; i < 1000; i++) {
            if (cur_n > capacity) {  // 判断的是i-1时的运载量
                return false;
            }
            cur_n += diff_vec[i];

        }
        return true;
    }

};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值