算法专练:分治

1.LeetCode:21合并两个有序链表

原题链接


        将两个升序链表合并为一个新的 升序 链表并返回。
        新链表是通过拼接给定的两个链表的所有节点组成的。

        示例 1:

        输入:l1 = [1,2,4], l2 = [1,3,4]

        输出:[1,1,2,3,4,4]

        示例 2:

        输入:l1 = [], l2 = []

        输出:[]

        示例 3:

        输入:l1 = [], l2 = [0]

        输出:[0]

        提示:

        两个链表的节点数目范围是 [0, 50]

        -100 <= Node.val <= 100

        l1 和 l2 均按 非递减顺序 排列


        这道题老经典了,做法也有很多,不过既然本篇文章叫分治所以写法上就利用了分治的思路,其实也就是先建立一个哨兵位,然后分别取两个链表中较小的进行尾插,如果一个链表到达了尾结点直接把另一个链到末尾即可。另外链表题目要特别注意空结点的情况。

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if(list1==nullptr)
        return list2?list2:nullptr;
        ListNode* vir=new ListNode();
        ListNode* p1=list1,*p2=list2;
        ListNode* cur=vir;
        while(p1&&p2)
        {
            ListNode* prev=p1->val<=p2->val?p1:p2;
            cur->next=prev;
            p1->val<=p2->val?p1=p1->next:p2=p2->next;
            cur=cur->next;
        }
        cur->next=p1?p1:p2;
        return vir->next;
    }
};

2.LeetCode1985. 找出数组中的第 K 大整数

原题链接


        给你一个字符串数组 nums 和一个整数 k 。nums 中的每个字符串都表示一个不含前导零的整数。

        返回 nums 中表示第 k 大整数的字符串。

        注意:重复的数字在统计时会视为不同元素考虑。例如,如果 nums 是 [“1”,“2”,“2”],那么 “2” 是最大的整数,“2” 是第二大的整数,“1” 是第三大的整数。

        示例 1:

        输入:nums = [“3”,“6”,“7”,“10”], k = 4

        输出:“3”

        示例 2:

        输入:nums = [“2”,“21”,“12”,“1”], k = 3

        输出:“2”

        示例 3:

        输入:nums = [“0”,“0”], k = 2

        输出:“0”

        提示:

        1 <= k <= nums.length <= 10^4

        1 <= nums[i].length <= 100

        nums[i] 仅由数字组成

        nums[i] 不含任何前导零


        比较简单的做法是我们利用sort来自定义排序规则然后直接返回第k大的数,这里我就自己写一个归并排序了。两种方法都试过之后发现stl的排序api真是优化了很多,虽然都是O(nlogn)级别的排序但是执行时间是差别真的很大。

class Solution {
    bool isBig(const string & a  , const string & b){
        if(a.size()!=b.size()){
            return a.size()>b.size();
        }
        return a>b;
    }
    void mergesort(vector<string>& nums,int l,int r){
        if(l>=r){
            return;
        }
        int mid=l+((r-l)>>1);
        mergesort(nums,l,mid);
        mergesort(nums,mid+1,r);
        vector<string> tmp;
        int p1=l,p2=mid+1;
        while(p1<=mid&&p2<=r){
            tmp.emplace_back(isBig(nums[p1],nums[p2])?nums[p1++]:nums[p2++]);
        }
        while(p1<=mid){
            tmp.emplace_back(nums[p1++]);
        }
        while(p2<=r){
            tmp.emplace_back(nums[p2++]);
        }
        for(int i=l;i<=r;++i){
            nums[i]=tmp[i-l];
        }
        //这就是归并排序的标准流程了只不过我没有额外实现merge函数罢了
    }
public:
    string kthLargestNumber(vector<string>& nums, int k) {
        int l=0,r=nums.size()-1;
        mergesort(nums,l,r);
        return nums[k-1];
    }
};

在这里插入图片描述
可以看到stl的排序api优化到了何种程度。

3.LeetCode:558. 四叉树交集


        二进制矩阵中的所有元素不是 0 就是 1 。

        给你两个四叉树,quadTree1 和 quadTree2。其中 quadTree1 表示一个 n * n 二进制矩阵,而 quadTree2 表示另一个 n * n 二进制矩阵。

        请你返回一个表示 n * n 二进制矩阵的四叉树,它是 quadTree1 和 quadTree2 所表示的两个二进制矩阵进行 按位逻辑或运算 的结果。

        注意,当 isLeaf 为 False 时,你可以把 True 或者 False 赋值给节点,两种值都会被判题机制 接受 。

        四叉树数据结构中,每个内部节点只有四个子节点。此外,每个节点都有两个属性:

        val:储存叶子结点所代表的区域的值。1 对应 True,0 对应 False;

        isLeaf: 当这个节点是一个叶子结点时为 True,如果它有 4 个子节点则为 False 。

        class Node {
public boolean val;
public boolean isLeaf;
public Node topLeft;
public Node topRight;
public Node bottomLeft;
public Node bottomRight;
}

        我们可以按以下步骤为二维区域构建四叉树:

        如果当前网格的值相同(即,全为 0 或者全为 1),将 isLeaf 设为 True ,将 val 设为网格相应的值,并将四个子节点都设为 Null 然后停止。
如果当前网格的值不同,将 isLeaf 设为 False, 将 val 设为任意值,然后将当前网格划分为四个子网格。

        使用适当的子网格递归每个子节点。

        如果你想了解更多关于四叉树的内容,可以参考 wiki 。

        四叉树格式:

        输出为使用层序遍历后四叉树的序列化形式,其中 null 表示路径终止符,其下面不存在节点。

        它与二叉树的序列化非常相似。唯一的区别是节点以列表形式表示 [isLeaf, val] 。

        如果 isLeaf 或者 val 的值为 True ,则表示它在列表 [isLeaf, val] 中的值为 1 ;如果 isLeaf 或者 val 的值为 False ,则表示值为 0 。

        示例 1:

        输入:quadTree1 = [[0,1],[1,1],[1,1],[1,0],[1,0]]
, quadTree2 = [[0,1],[1,1],[0,1],[1,1],[1,0],null,null,null,null,[1,0],[1,0],[1,1],[1,1]]

        输出:[[0,0],[1,1],[1,1],[1,1],[1,0]]

        示例 2:

        输入:quadTree1 = [[1,0]]
, quadTree2 = [[1,0]]

        输出:[[1,0]]

        示例 3:

        输入:quadTree1 = [[0,0],[1,0],[1,0],[1,1],[1,1]]
, quadTree2 = [[0,0],[1,1],[1,1],[1,0],[1,1]]

        输出:[[1,1]]

        示例 4:

        输入:quadTree1 = [[0,0],[1,1],[1,0],[1,1],[1,1]]
, quadTree2 = [[0,0],[1,1],[0,1],[1,1],[1,1],null,null,null,null,[1,1],[1,0],[1,0],[1,1]]

        输出:[[0,0],[1,1],[0,1],[1,1],[1,1],null,null,null,null,[1,1],[1,0],[1,0],[1,1]]

        示例 5:
输入:quadTree1 = [[0,1],[1,0],[0,1],[1,1],[1,0],null,null,null,null,[1,0],[1,0],[1,1],[1,1]]
, quadTree2 = [[0,1],[0,1],[1,0],[1,1],[1,0],[1,0],[1,0],[1,1],[1,1]]

        输出:[[0,0],[0,1],[0,1],[1,1],[1,0],[1,0],[1,0],[1,1],[1,1],[1,0],[1,0],[1,1],[1,1]]

        提示:

        quadTree1 和 quadTree2 都是符合题目要求的四叉树,每个都代表一个 n * n 的矩阵。

        n == 2^x ,其中 0 <= x <= 9.


        这道题如果不熟悉四叉树的话就纯纯的是阅读理解了(说的就是我),那么这道题是什么意思呢?这里四叉树指的是吧一个矩阵四等分为一个四叉树的结点,这个结点是叶子结点的条件是他的四个子结点都是叶子节点且他们的值都相等。那么题目中的按位逻辑或是什么意思呢,这里我的理解是对于两棵四叉树,如果对于某个结点来说,如果这两棵树的对应节点有一个是叶子节点并且这个叶子结点的值为1的时候我们返回他本身,如果他的值不为1我们直接返回另一个结点(这里就是按位或的意义了,我们假设第一棵树的是叶子节点但他为false,这个时候他跟另一棵树按位或的话不会影响另一棵树的值,如果他本身是true的话,另一颗树的值不会影响他的值);如果这两棵树均不是叶子结点我们去看这个结点的四个子节点,直到找到了某个对应子节点为叶子节点为止。

        这样说可能会很懵,看过代码梳理一遍的话可能更好理解:

class Solution {
public:
    Node* intersect(Node* quadTree1, Node* quadTree2) {
        if(quadTree1->isLeaf){
            return quadTree1->val?quadTree1:quadTree2;
        }else if(quadTree2->isLeaf){
            return quadTree2->val?quadTree2:quadTree1;
        }
        //这里就是如果其中一个是叶子节点并且值为1的时候返回本身不是1返回另一个
        quadTree1->topLeft=intersect(quadTree1->topLeft,quadTree2->topLeft);
        quadTree1->topRight=intersect(quadTree1->topRight,quadTree2->topRight);
        quadTree1->bottomLeft=intersect(quadTree1->bottomLeft,quadTree2->bottomLeft);
        quadTree1->bottomRight=intersect(quadTree1->bottomRight,quadTree2->bottomRight);
        //这里是两个节点都不是叶子节点递归去吧四个子节点结合起来。
        if(quadTree1->topLeft->isLeaf 
            && quadTree1->topRight->isLeaf 
            &&quadTree1->bottomLeft->isLeaf  
            && quadTree1->bottomRight->isLeaf 
            && quadTree1->topLeft->val == quadTree1->topRight->val
            && quadTree1->topRight->val == quadTree1->bottomLeft->val
            && quadTree1->bottomLeft->val == quadTree1->bottomRight->val){
                quadTree1->isLeaf=true;
                quadTree1->val=quadTree1->topLeft->val;
                quadTree1->topLeft=nullptr;
                quadTree1->topRight=nullptr;
                quadTree1->bottomLeft=nullptr;
                quadTree1->bottomRight=nullptr;
            }
            //四个子节点都结合完毕之后对本身操作,如果四个节点都是叶子节点并且值都相等,就把他自己置为叶子节点,值也变为子节点的值
        return quadTree1;
    }
};

4.LeetCode:932. 漂亮数组

原题链接


        对于某些固定的 N,如果数组 A 是整数 1, 2, …, N 组成的排列,使得:

        对于每个 i < j,都不存在 k 满足 i < k < j 使得 A[k] * 2 = A[i] + A[j]。

        那么数组 A 是漂亮数组。

        给定 N,返回任意漂亮数组 A(保证存在一个)。

        示例 1:

        输入:4

        输出:[2,1,4,3]

        示例 2:

        输入:5

        输出:[3,1,2,5,4]

        提示:

        1 <= N <= 1000


        这道题。。。。。我能说是懵的吗,题目要求a[k]*2!=a[i]+a[j],对于左边来说一定是一个偶数,对于右边来说让他们不相等最简单的就是让其等于奇数,而奇数=奇数+偶数(奇数加奇数是偶数),所以这里我们要做的就是要将数组拆分成两部分,前半部分为奇数,后半部分为偶数。于是这时候问题又来了,当N=6的时候奇数部分为[1,3,5],偶数部分为[2,4,6]这显然是保证不了每个a[i]+a[k]都是奇数的,但是这个时候奇数部分加偶数部分一定是奇数,我们接下来要做的就是让奇数部分和偶数部分相加不符合条件即可。观察条件我们不只有让右边和左边奇偶性不同,最本质的条件是他们的值不同,对于奇偶拆分后的数组,我们不断的对这些数组进行如上的操作,这样就会使数组中较大的数会过早的出现在数组的前方,而较小的数会出现在数组的后方。(这里解释的完全十分不严谨,建议还是去看一下题解虽然不一定看得懂。。。。)

class Solution {
public:
    void merge(vector<int>& ans){
        if(ans.size()<=2){
            return ;
        }
        vector<int> odd,even;
        for(int i=0;i<ans.size();i+=2){
            even.push_back(ans[i]);
        }
        for(int i=1;i<ans.size();i+=2){
            odd.push_back(ans[i]);
        }
        merge(odd);
        merge(even);
        int idx=0;
        for(int i=0;i<even.size();++i){
            ans[idx++]=even[i];
        }
        for(int i=0;i<odd.size();++i){
            ans[idx++]=odd[i];
        }
    }
    vector<int> beautifulArray(int n) {
        vector<int> ans;
        for(int i=1;i<=n;++i){
            ans.push_back(i);
        }
        merge(ans);
        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值