12.06-12.15力扣分治法刷题

【1】53. 最大子数组和

日期:12.06

1.题目链接:53. 最大子数组和 - 力扣(LeetCode)https://leetcode.cn/problems/maximum-subarray/description/?envType=problem-list-v2&envId=divide-and-conquer

2.类型:线段树,动态规划,分治

3.方法一:动态规划(一次题解)

对于数组中的每个位置,计算以该位置元素结尾的最大子数组和,然后取所有位置中的最大值作为结果。

动态规划转移方程:f(i)=max{f(i−1)+nums[i],nums[i]}

关键代码:

cpp 复制代码
        for(const auto &x:nums){
            pre=max(pre+x,x);
            maxNum=max(pre,maxNum);
        }

4.方法二:分治法(半解)

核心数据结构:

lSum :以区间左端点开始的最大子数组和

rSum :以区间右端点结束的最大子数组和

mSum :区间的最大子数组和(不限制位置)

iSum :区间的总和

合并状态(核心逻辑):

cpp 复制代码
Status pushUp(Status l, Status r) {
    int iSum = l.iSum + r.iSum;  // 总和 = 左区间和 + 右区间和
    int lSum = max(l.lSum, l.iSum + r.lSum);  // 要么是左区间的lSum,要么跨越到右区间
    int rSum = max(r.rSum, r.iSum + l.rSum);  // 要么是右区间的rSum,要么跨越到左区间
    int mSum = max(max(l.mSum, r.mSum), l.rSum + r.lSum);  // 最大子数组可能完全在左、完全在右、或跨越中点
    return (Status) {lSum, rSum, mSum, iSum};
}

关键代码:

cpp 复制代码
    // 递归获取区间[l, r]的状态
    Status get(vector<int> &a, int l, int r){
        // 基本情况:区间只有一个元素
        if(l==r){
            return (Status) {a[l], a[l], a[l], a[l]};
        }       
        // 计算中点
        int m=(l+r)>>1;        
        // 递归获取左右子区间的状态
        Status lSub=get(a,l,m);
        Status rSub=get(a,m+1,r);        
        // 合并两个子区间的状态
        return pushUp(lSub, rSub);
    }

【2】190. 颠倒二进制位

日期:12.07

1.题目链接:190. 颠倒二进制位 - 力扣(LeetCode)https://leetcode.cn/problems/reverse-bits/description/?envType=problem-list-v2&envId=divide-and-conquer

2.类型:分治,位运算

3.方法一:逐位颠倒(官方题解)

逐位操作详解:

  1. 取出最低位:n & 1:

& 1操作可以取出一个数的最低位(第0位)

如果最低位是1,结果为1;如果最低位是0,结果为0

  1. 左移到正确位置:<< (31 - i)

原数的第0位(最低位)应该成为反转后的第31位(最高位)

原数的第1位应该成为反转后的第30位

原数的第31位应该成为反转后的第0位

所以,原数的第i位应该左移(31 - i)

  1. 设置到结果中:rev |= ...

|=是按位或赋值操作

将左移后的位设置到rev的对应位置

因为rev初始为0,所以直接或操作即可

  1. 右移原数:n >>= 1

n右移一位,丢弃已处理的最低位

下次循环处理下一个位

关键代码:

cpp 复制代码
        // 循环32次,处理32位
        for(int i=0;i<32&&n>0;++i){
            // 1. 取出n的最低位:n & 1
            // 2. 将这个位左移到反转后的位置:(31 - i)
            // 3. 通过按位或(|)操作设置到rev的对应位置
            rev |= (n & 1) << (31 - i);
            n >>= 1;
        }

【3】191. 位1的个数

日期:12.08

1.题目链接:191. 位1的个数 - 力扣(LeetCode)https://leetcode.cn/problems/number-of-1-bits/description/?envType=problem-list-v2&envId=divide-and-conquer

2.类型:分治,位运算

3.方法一:循环检查二进制位(一次题解)

  1. 创建位掩码:1 << i

1的二进制:00000000000000000000000000000001

1 << i:将1左移i位,创建一个只在第i位为1的掩码

  1. 检查特定位:n & (1 << i)

按位与操作&:只有两个位都为1时,结果位才为1

如果n的第i位为1,那么n & (1 << i)的结果非0

如果n的第i位为0,那么n & (1 << i)的结果为0

  1. 条件判断:if (n & (1 << i))

如果表达式结果为非0,进入if语句块,计数器加1

如果表达式结果为0,跳过if语句块

关键代码:

cpp 复制代码
       for(int i=0;i<32;i++){
            // (1<<i):创建一个只在第i位为1的掩码
            // n&(1<<i):如果n的第i位为1,结果非0;否则为0
            if(n&(1<<i)){
                ret++;  // 如果第i位为1,计数器加1
            }
        }

【4】215. 数组中的第K个最大元素 - 力扣(LeetCode)

日期:12.09

1.题目链接215. 数组中的第K个最大元素 - 力扣(LeetCode)https://leetcode.cn/problems/kth-largest-element-in-an-array/solutions/307351/shu-zu-zhong-de-di-kge-zui-da-yuan-su-by-leetcod-2/?envType=problem-list-v2&envId=divide-and-conquer

2.类型:分治,位运算

3.方法一:基于快速排序的选择方法(官方题解)

分解: 将数组 a[l⋯r] 「划分」成两个子数组 a[l⋯q−1]、a[q+1⋯r],使得 a[l⋯q−1] 中的每个元素小于等于 a[q],且 a[q] 小于等于 a[q+1⋯r] 中的每个元素。其中,计算下标 q 也是「划分」过程的一部分。

解决: 通过递归调用快速排序,对子数组 a[l⋯q−1] 和 a[q+1⋯r] 进行排序。

合并: 因为子数组都是原址排序的,所以不需要进行合并操作,a[l⋯r] 已经有序。

上文中提到的 「划分」 过程是:从子数组 a[l⋯r] 中选择任意一个元素 x 作为主元,调整子数组的元素使得左边的元素都小于等于它,右边的元素都大于等于它, x 的最终位置就是 q。

关键代码:

cpp 复制代码
    if (l==r)
            return nums[k];       
        int partition=nums[l];
        int i=l-1,j=r+1;       
        // Hoare分区方案
        while(i<j){
            // 从左向右找到第一个≥基准的元素
            do i++; while(nums[i]<partition);
            // 从右向左找到第一个≤基准的元素
            do j--; while(nums[j]>partition);
            // 如果i<j,交换这两个元素
            if(i<j)
                swap(nums[i], nums[j]);
        }        
        // 递归处理包含第k个元素的那一半
        if(k<=j)
            return quickselect(nums,l,j,k);
        else
            return quickselect(nums,j+1,r,k);
    }

【5】324. 摆动排序 II

日期:12.10

1.题目链接:324. 摆动排序 II - 力扣(LeetCode)https://leetcode.cn/problems/wiggle-sort-ii/description/?envType=problem-list-v2&envId=divide-and-conquer

2.类型:分治,排序

3.方法一:排序(一次题解)

先对数组排序

将数组分成两半:前半部分(较小的一半)和后半部分(较大的一半)

从后向前分别取前半部分和后半部分的元素,交叉放入结果数组中

关键代码:

cpp 复制代码
        int n=nums.size();
        vector<int> arr=nums;
        sort(arr.begin(),arr.end());        
        // 计算分界点:将数组分成两半
        int x=(n+1)/2;         
        // 交叉放置:前半部分放偶数位,后半部分放奇数位
        //    从后向前取,避免相同元素相邻
        for (int i=0,j=x-1,k=n-1;i<n;i+=2,j--,k--){
            // 偶数位置:放前半部分的元素
            nums[i]=arr[j];            
            // 奇数位置:放后半部分的元素
            if(i+1<n){
                nums[i+1]= arr[k];
            }
        }

【6】372. 超级次方

日期:12.11

1.题目链接:372. 超级次方 - 力扣(LeetCode)https://leetcode.cn/problems/super-pow/description/?envType=problem-list-v2&envId=divide-and-conquer

2.类型:分治,排序

3.方法一:倒序遍历(半解)

关键代码:

cpp 复制代码
    int pow(int x,int n){
        int res=1;
        x%=MOD;  // 先取模,确保x在模范围内        
        while(n){
            // 如果n是奇数,将当前x乘入结果
            if(n%2){
                // 使用long防止乘法溢出
                res=(long) res*x%MOD;
            }
            x=(long) x*x%MOD;
            n/=2;
        }
        return res;
    }

【7】973. 最接近原点的 K 个点

日期:12.12

1.题目链接:973. 最接近原点的 K 个点 - 力扣(LeetCode)https://leetcode.cn/problems/k-closest-points-to-origin/description/?envType=problem-list-v2&envId=divide-and-conquer

2.类型:分治,排序

3.方法一:排序(一次题解)

计算每个点到原点的距离(平方,避免开方运算)

按照距离从小到大排序

返回前K个点

关键代码:

cpp 复制代码
sort(points.begin(), points.end(), [](const vector<int>& u, const vector<int>& v) {
            return u[0]*u[0]+u[1]*u[1]<v[0]*v[0]+v[1]*v[1];
        });
        return {points.begin(), points.begin() + k};

【8】1738. 找出第 K 大的异或坐标值

日期:12.13

1.题目链接:1738. 找出第 K 大的异或坐标值 - 力扣(LeetCode)https://leetcode.cn/problems/find-kth-largest-xor-coordinate-value/description/?envType=problem-list-v2&envId=divide-and-conquer

2.类型:分治,前缀和,矩阵

3.方法一:二维前缀和 + 排序(半解)

二维前缀异或:类似二维前缀和,但使用异或运算

动态规划公式:pre[i][j] = pre[i-1][j] ^ pre[i][j-1] ^ pre[i-1][j-1] ^ matrix[i-1][j-1]

收集所有值并排序:找出第K大的值

关键代码:

cpp 复制代码
 //计算每个位置的前缀异或
        for(int i=1;i<=m;++i){
            for(int j=1;j<=n;++j){
                pre[i][j]=pre[i-1][j] ^      // 上方矩形
                          pre[i][j - 1] ^      // 左方矩形
                          pre[i - 1][j - 1] ^  // 左上方矩形
                          matrix[i - 1][j - 1];// 当前元素                
                results.push_back(pre[i][j]);
            }
        }

【9】1985. 找出数组中的第 K 大整数

日期:12.14

1.题目链接:1985. 找出数组中的第 K 大整数 - 力扣(LeetCode)https://leetcode.cn/problems/find-the-kth-largest-integer-in-the-array/description/?envType=problem-list-v2&envId=divide-and-conquer

2.类型:分治,字符串

3.方法一:自定义排序(半解)

数字字符串的比较规则:

首先比较长度:长度越长的数字越大

长度相同则按字典序比较:由于都是数字字符,字典序与数值序一致

按降序排列,取第K大的元素

关键代码:

cpp 复制代码
    auto cmp=[](const string& s1, const string& s2) -> bool {
            // 首先比较字符串长度
            if(s1.size()>s2.size()){
                return true;  // s1长度更大,所以数值更大
            }
            else if(s1.size()<s2.size()){
                return false; // s1长度更小,所以数值更小
            }
            else {
                // 对于数字字符串,字典序大的数值也大
                return s1 > s2;
            }
        };

【10】2343. 裁剪数字后查询第 K 小的数字

日期:12.15

1.题目链接:2343. 裁剪数字后查询第 K 小的数字 - 力扣(LeetCode)https://leetcode.cn/problems/query-kth-smallest-trimmed-number/description/?envType=problem-list-v2&envId=divide-and-conquer

2.类型:分治,排序

3.方法一:直接排序(半解)

对于每个查询:

根据 trim 参数,比较每个数字的最后 trim

使用稳定排序,当数字相同时保持原有顺序

排序后取第 k 小的数字对应的原索引

关键代码:

cpp 复制代码
       for(int i=0;i<queries.size();++i){
            auto &q=queries[i];           // 当前查询 [k, trim]           
            iota(idx, idx + n, 0);            
            //按照裁剪后的数字排序
            stable_sort(idx, idx + n, [&](int a, int b){
                auto &s=nums[a],&t=nums[b];                
                // 比较最后 trim 位
                for(int j=m-q[1];j<m;++j){
                    if(s[j]!=t[j]){
                        return s[j]<t[j];
                    }
                }
                return false;
            });
            ans[i]=idx[q[0]-1];
        }
相关推荐
月明长歌6 小时前
【码道初阶】【牛客BM30】二叉搜索树与双向链表:java中以引用代指针操作的艺术与陷阱
java·数据结构·算法·leetcode·二叉树·笔试·字节跳动
hoiii1876 小时前
使用RPCA算法对图像进行稀疏低秩分解
人工智能·算法
yuuki2332336 小时前
【C++】内存管理
java·c++·算法
刃神太酷啦6 小时前
Linux 进程核心原理精讲:从体系结构到实战操作(含 fork / 状态 / 优先级)----《Hello Linux!》(6)
java·linux·运维·c语言·c++·算法·leetcode
一个不知名程序员www6 小时前
算法学习入门---二叉树
c++·算法
小李小李快乐不已6 小时前
数组&&矩阵理论基础
数据结构·c++·线性代数·算法·leetcode·矩阵
feifeigo1236 小时前
SVM分类在高光谱遥感图像分类与预测中的应用
算法·支持向量机·分类
三川6986 小时前
AVL树的学习
数据结构·学习·算法
测试人社区-小明6 小时前
未来测试岗位的AI需求分析
人工智能·opencv·测试工具·算法·金融·机器人·需求分析