• leetcode刷题(一)


    41. 缺失的第一个正数

    给你一个未排序的整数数组,请你找出其中没有出现的最小的正整数。

    题解

    要求空间复杂度O(1)。前不久刚做了一道循环排列的题,所以看见“就地”两个字,秒懂: 将数字放到对应的位置上

    有个特殊的样例,nums[i] - 1可能会超出int的范围

    class Solution {
    public:
        int firstMissingPositive(vector<int>& nums) {
            int n = nums.size();
            for (int i = 0; i < n; ++i) if (nums[i] >= 0) nums[i]--;
    
            for (int i = 0; i < n; ++i) {
                int pos = nums[i];
                while(pos >= 0 && pos < n && pos != nums[pos]) swap(pos, nums[pos]);
            }
    
            int ans = n + 1;
            for (int i = 0; i < n; ++i) if (nums[i] != i) {
                ans = i + 1;
                break;
            }
            return ans;
        }
    };
    

    287. 寻找重复数

    给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数

    题解一

    要求空间复杂度O(1)、时间复杂度小于O((n^2))、不能更改原数组。二分枚举答案,遍历整个数组,如果小于或者等于枚举的答案的个数大于枚举的答案,说明左半段出现了重复,否则,说明右半段出现了重复。

    妙啊!原味:https://leetcode-cn.com/problems/find-the-duplicate-number/solution/er-fen-fa-si-lu-ji-dai-ma-python-by-liweiwei1419/

    class Solution {
    public:
        int findDuplicate(vector<int>& nums) {
            int n = nums.size();
            int l = 1, r = n;
    		
            // 时间换空间,有点儿意思
            while(l < r) {
                int mid = (l + r) >> 1;      // 枚举答案
    
                int cnt = 0;
                for (int num: nums) if (num <= mid) cnt++;
    
                if (cnt > mid) r = mid;
                else  l = mid + 1;
            }
    
            return l;
        }
    };
    

    题解二

    oooooorz,还可以用二进制考虑。思想就一句话:如果一个数是重复的,那么对应的二进制位中1的个数肯定比原来多,因为重复了嘛。

    详细讲解:https://leetcode-cn.com/problems/find-the-duplicate-number/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by--52/

    // beautiful code
    class Solution {
    public:
        int findDuplicate(vector<int>& nums) {
            int n = nums.size();
            int ans = 0;
            for (int i = 0; i < 32; ++i) {
                int a = 0, b = 0;
                int mask = 1 << i;
                for (int j = 0; j < n; ++j) {
                    if (nums[j] & mask) a++;
                    if (j & mask) b++;
                }
                if (a > b) ans += mask;  // ans = ans | mask;  is ok!
            }
            return ans;
        }
    };
    
    // sad code
    class Solution {
    public:
        int findDuplicate(vector<int>& nums) {
            int n = nums.size();
    
            int bit_cnt[40];
            memset(bit_cnt, 0, sizeof(bit_cnt));
    
            for (int num: nums) {
                int t = 0;
                while(num) bit_cnt[t++] += (num & 1), num >>= 1;
            }
    
            int bit_tot[40];
            memset(bit_tot, 0, sizeof(bit_tot));
    
            for (int i = 1; i < n; ++i) {
                int t = 0, num = i;
                while(num) bit_tot[t++] += (num & 1), num >>= 1;
            }
    
            int ans = 0;
            for (int i = 0; i < 32; ++i) if (bit_tot[i] < bit_cnt[i]) ans += (1 << i);
            return ans;
        }
    };
    

    题解三

    (n + 1)(1)(n)之间的数(包括(1)(n))构成的数组,以下标(从0开始)和值建边构成的有向图必定含有环,因为有重复的数。其实任意一个排列(下标从1开始),按照这种方式构成的图都是由一个个环组成的。在这道题中环的入口就是重复的数,怎么求环的入口?快慢指针法,常用来判断单链表中是否有环,详细说明请看代码

    神人之笔

    class Solution {
    public:
        int findDuplicate(vector<int>& nums) {
            int slow = nums[0];                  // 慢指针
            int fast = nums[nums[0]];            // 快指针
    
            while(slow != fast) {                // 相等,代表在环中相遇
                slow = nums[slow];
                fast = nums[nums[fast]];         
            }
    
    

    求环的入口?
    令环的长度为(m),从0走到环的入口需要(S)
    假设慢指针走了(n)步与快指针在环内相遇,那么快指针走了(2*n)步,且(2 * n - n = n)(n \% m = 0)。在第一次相遇的时候,慢指针在环内走了(n - S)步。令fast = 0,然后走(S)步走到环的入口,慢指针同时移动也走了(S)步,算上之前,慢指针在环内共走了(n - S + S = n)步,恰好走到环的入口((n \% m = 0)),于是快慢指针在入口相遇。

            fast = 0;
            while(slow != fast) {             // 第二次相遇必定在入口处
                fast = nums[fast];
                slow = nums[slow];
            }
            return fast;
        }
    };
    

    765. 情侣牵手

    N 对情侣坐在连续排列的 2N 个座位上,想要牵到对方的手。 计算最少交换座位的次数,以便每对情侣可以并肩坐在一起。 一次交换可选择任意两人,让他们站起来交换座位。

    人和座位用 0 到 2N-1 的整数表示,情侣们按顺序编号,第一对是 (0, 1),第二对是 (2, 3),以此类推,最后一对是 (2N-2, 2N-1)。

    这些情侣的初始座位 row[i] 是由最初始坐在第 i 个座位上的人决定的。

    题解一

    很容易想到先把情侣的一人固定在当前位置上,然后交换另一个人来凑成情侣。就是不知道这样为啥能得到最优解,想不通,orz。复杂度(O(n^2))

    class Solution {
    public:
        int minSwapsCouples(vector<int>& row) {
            int n = row.size();
            int ans = 0;
            
            for (int i = 0; i < n; i += 2) {
                if (judeg(row[i],  row[i + 1])) continue;
                for (int j = i + 2; j < n; j += 2) {
                    if (judeg(row[j], row[j + 1])) continue;
                    if (judeg(row[i], row[j])) {
                        swap(row[i + 1], row[j]);
                        ans++;
                        break;
                    }
                    else if (judeg(row[i], row[j + 1])) {
                        swap(row[i + 1], row[j + 1]);
                        ans++;
                        break;
                    }
                }
            }
    
            return ans;
        }
        bool judeg(int a, int b) {
            if (a > b) swap(a, b);
            if (b - a > 1) return false;
            if (!(a & 1) && (b & 1)) return true;
            return false;
        }
    };
    

    153. 寻找旋转排序数组中的最小值

    假设按照升序排序的数组在预先未知的某个点上进行了旋转。
    ( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
    请找出其中最小的元素。
    你可以假设数组中不存在重复元素。

    题解

    时间复杂度要求(O(log_2(n))),二分,然后找判断是在左区域还是右区域的条件

    写惯了开区间的二分,上来就RE

    class Solution {
    public:
        int findMin(vector<int>& nums) {
            int l = 0, r = nums.size() - 1;
            while(l < r) {
                int mid = (l + r) >> 1;
                if (nums[mid] <= nums[r]) r = mid;
                else l = mid + 1;
            }
            return nums[l];
            /*
            整理一下就是上面的代码
            for(int i = 0; i < 40; ++i) {
                int mid = (l + r) >> 1;
                if (nums[l] <= nums[mid]) {
                    if(nums[r] > nums[mid]) r = mid;      // nums[r],r一定要小于n
                    else l = mid;
                }
                else r = mid;   // 不要写成 l = mid,因为可能跳过了最小值
            }
            return min(nums[l], nums[r]);
            */
        }
    };
    

    154. 寻找旋转排序数组中的最小值 II【好题】

    假设按照升序排序的数组在预先未知的某个点上进行了旋转。
    ( 例如,数组 [0,1,1,4,5,6,7] 可能变为 [4,5,6,7,0,1,1] )。
    请找出其中最小的元素。
    假设存在重复的数字

    题解

    时间复杂度要求在([log_2(n), n])之间。显然整个数组被分成了两个上升段。二分,只是需要判断下面这三种情况,边界应该怎么移动:

    • nums[mid] < nums[r],说明midr处在同一个上升段内,且不会存在比nums[mid]更小的数,所以r = mid
    • nums[mid] == nums[r],此时无法判断答案在哪边,举例:[3,1,3,3,3][3,3,3,1,3],都满足等式,但最小值可能出现在mid之前也可能之后,所以只能令r = r - 1,逐步缩小查找范围。为什么不能l++,由样例1可知,这样更新边界可能忽略最小值。
    • nums[mid] > nums[r],说明midl处在同一个上升段内,而答案在另一个上升段内,所以l = mid + 1

    看了一下讨论,发现可做以下几个扩展:①找出旋转点的位置(但存在无法找到旋转点位置的情况,如[1,1,1,1])。②如果是多次旋转,能不能设计较优的算法找到最小值

    突然觉得leetcode上面的题很有趣啊,整道题的灵魂就是r--,以及考察特殊情形。妙,甚妙!总结:脑子不够灵活

    while(l < r) {
        int mid = (l + r) >> 1;
        if (nums[mid] < nums[r]) r = mid;
        else if (nums[mid] == nums[r]) r--;
        else l = mid + 1;
    }
    return nums[l];
    

    33. 搜索旋转排序数组

    假设按照升序排序的数组在预先未知的某个点上进行了旋转。
    ( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
    搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
    你可以假设数组中不存在重复的元素。
    你的算法时间复杂度必须是(O(log_2(n)))级别

    题解

    二分,关键在于判断lmidr的分布,分类讨论:

    • nums[mid] < nums[r]说明midr在同一个上升段,l是否与它们在同一个上升段需要讨论
      • target < nums[mid],说明targetmid的左边
      • target > nums[mid]target可能在mid的左边,既在第一个上升段内,也可能在mid的右边,需要讨论
    • nums[mid] > nums[r]说明midl在同在第一个上升段
      • target < nums[mid]target可能在mid的左边,既在第一个上升段内,也可能在mid的右边,即在第二个上升段内,需要讨论
      • target > nums[mid],说明targetmid的右边

    这分类讨论,酸爽!其实正解是先用上一题的方法,先找到旋转点,既最小值的位置(pos),然后分别在两个上升段内(([0, pos - 1], [pos + 1, r]))用二分查找。

    class Solution {
    public:
        int search(vector<int>& nums, int target) 
        {
            int l = 0, r = nums.size() - 1;
            if (r < 0) return -1;                // 坑,可能输入空数组
            while(l < r){
                int mid = (l + r) >> 1;
                if (nums[mid] == target) return mid;
    
                if (nums[mid] < nums[r]) {
                    if (target < nums[mid]) r = mid;
                    else {
                        /* think! why this code is wrong ?
    
                        if (nums[l] > target) l = mid + 1;
                        else r = mid;
    
                        */
                        // target > nums[mid],讨论需仔细
                        if (nums[l] > nums[r]) {
                            if (nums[l] > target) l = mid + 1;
                            else r = mid;
                        }
                        else l = mid + 1;
                    }
                }
                else {
                    if (target < nums[mid]) {
                        if (nums[l] > target) l = mid + 1;
                        else r = mid;
                    }
                    else l = mid + 1;
                }
            }
            return nums[l] == target ? l : -1;
        }
    };
    

    81. 搜索旋转排序数组 II

    假设按照升序排序的数组(可能有重复数字)在预先未知的某个点上进行了旋转。
    ( 例如,数组 [0,0,1,2,2,5,6] 可能变为 [2,5,6,0,0,1,2] )。
    编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true,否则返回 false。

    题解

    二分,依然是分类讨论,当有重复数字时,会导致边界无法确定更新,老办法,令r = r - 1

    灵魂:r--,和上一道题相比,这题只能分类讨论,因为它的旋转位置可能无法确定,比如[1,1,1,1]。越想越妙啊,如果无法确定mid处在哪个上升段,就缩小r

    while(l < r){
        int mid = (l + r) >> 1;
        if (nums[mid] == target) return true;
    
        if (nums[mid] < nums[r]) {
            if (target < nums[mid]) r = mid;
            else {
                if (nums[l] >= nums[r]) {      // 与上题相比,多了一个等号,因为相等也意味着l在第一个上升段
                    if (nums[l] > target) l = mid + 1;
                    else r = mid;
                }
                else l = mid + 1;
            }
        }
        else if (nums[mid] == nums[r]) {      // mid可能在第一个上升段,也可能在第二个上升段,所以直接令 r = r - 1
            r--;
        }
        else {
            if (target < nums[mid]) {
                if (nums[l] > target) l = mid + 1;
                else r = mid;
            }
            else l = mid + 1;
        }
    }
    
  • 相关阅读:
    设计模式原则—依赖倒转原则(三)
    设计模式原则—单一职责原则(二)
    一步一个脚印学习WCF系列之WCF基础术语—契约的名称与命名空间(二)
    命名规范汇总文档供下载
    BCM平台全自动刷机软件,TFTP正式版1.62隆重发布,增加固件记忆功能
    WayOs内置免拉黑,就是把免拉黑程序集成在WayOs内部,增加守护进程及修改访问参数立即生效
    WayOs帐号获取保存工具增加提交的功能,WayOs扩展WAN口工具1.6发布增加网卡和VLAN混合组网功能
    好奇:WayOs破解、OEM、修复、打包等工具大全,满足大家的好奇心发下截图
    WayOs PPPoE群集服务器:自动同步用户信息,包括增加,更新,同步操作!保证多台服务器数据同步
    WayOs全面支持安卓手机,防二级路由开关、充值卡到期自动跳转页面功能隆重发布
  • 原文地址:https://www.cnblogs.com/zgglj-com/p/12755985.html
Copyright © 2020-2023  润新知