• c++ LeetCode(初级数组篇)十一道算法例题代码详解(一)


    原文作者:aircraft

    原文链接:https://www.cnblogs.com/DOMLX/p/10940636.html

        唉!最近忙着面试找实习,然后都是面试的很多是leetcode的算法题,所以自己就刷了一遍,并且做些笔记,以后再来复习好了,悲催的大学生。。。。。

    一套面试题的目录在此,还在继续完善中。。。。。。

    c/c++ 2019面试题目录

    一、从(排序!)数组中删除重复项

     

    给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。

    不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。

    示例 1:

    
    
    给定数组 nums = [1,1,2], 
    
    函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 
    
    你不需要考虑数组中超出新长度后面的元素。
    
    
    
     

    示例 2:

    
    
    给定 nums = [0,0,1,1,1,2,2,3,3,4],
    
    函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。
    
    你不需要考虑数组中超出新长度后面的元素。
    
    
    
    
    

    说明:

    为什么返回数值是整数,但输出的答案是数组呢?

    请注意,输入数组是以“引用”方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

    你可以想象内部操作如下:

    // nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
    int len = removeDuplicates(nums);
    
    // 在函数里修改输入数组对于调用者是可见的。
    // 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。
    for (int i = 0; i < len; i++) {
        print(nums[i]);
    }

    我第一次自写的代码(因为审题不认真,写成了无序数组的删除QAQ---):

    用时300ms.

    class Solution {
    public:
        int removeDuplicates(vector<int>& nums) {
            vector<int> my_nums;
            bool same = 0;
            bool first = 1;
            for (auto num1 : nums) {
                if (first) {
                    my_nums.emplace_back(num1);
                    first = 0;
                    continue;
                }
                    for (auto num2 : my_nums) {
                        if (num1 == num2) {
                            same = 1;
                            break;
                        }
    
                    }
                    if (!same) my_nums.emplace_back(num1);
                    same = 0;
    
            }
            for (int i = 0; i < my_nums.size(); i++) {
                nums[i] = my_nums[i];
            }
            return my_nums.size();
        }
    };

    大佬们写的示例代码():

    用时30ms左右:

    class Solution {
    public:
        int removeDuplicates(vector<int>& nums) {
            int length = nums.size();
            if(length == 0)
                return 0;
            int flag = 0;
            for(int i = 1; i < length; i++){
                while(nums[i] == nums[flag]){
                    if(i < length-1)
                        i++;
                    else
                        break;
                }
                    flag++;
                    nums[flag] = nums[i];
            }
            return (flag + 1);
        }
    };

    用时20左右ms

    class Solution {
    public:
        int removeDuplicates(vector<int>& nums) {
            
            int n = nums.size();
            if(n == 0 || n == 1)
                return n;
            int index=0;
            int i = 1;
            while(i<n)
            {
                if(nums[index] != nums[i])
                {
                    index++;
                    nums[index] = nums[i];
                }
                i++;
            }
            return index+1;
        }
    };

    自我反思:因为题目看错导致跟大佬们的代码对比反思不多所以就总结两点

    1.  刚开始刷LeetCode没有认真对待,没有审题清楚。
    2.    即使是写成无序数组的删除还是没有考虑到传入参数为0和1的情况。 

    、买卖股票的最佳时机 II

    给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

    设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

    注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

    示例 1:

    输入: [7,1,5,3,6,4]
    输出: 7
    解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
         随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
    

    示例 2:

    输入: [1,2,3,4,5]
    输出: 4
    解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
         注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
         因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
    

    示例 3:

    输入: [7,6,4,3,1]
    输出: 0
    解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

    我的代码:

    12ms左右

    class Solution {
    public:
        int maxProfit(vector<int>& prices) {
        int sell;
        int sell_value = 0;
        int buy_value = 0;
        int profit = 0;
        int len = prices.size();
        if (len < 2) return 0;
        int i = 0;
        int index;
    
        while (i < len) {
            index = i + 1;
            if (index < len) {
                if (prices[i] < prices[index]) {
                    buy_value = prices[i];
                    sell_value = prices[index];
                    sell = index;
                    while (++index < len) {
                        if (prices[sell] > prices[index]) {
                            profit += sell_value - buy_value;
                            i = index - 1;
                            buy_value = sell_value = 0;
                            break;
                        }
                        else {
                            sell = index;
                            sell_value = prices[index];
                        }
                    }
                }
            }
            if (index == len)break;
            i++;
        }
        profit += sell_value - buy_value;
        return profit;
    }
    };

    先讲讲我一开始看到这道题目是怎么分析的,从题意上看:

    1. 数组从大到小例如[5,4,3,2,1]的就直接没有利润,利润置为0;
    2. 数组从小到大的直接返回最后的减去最前的就是利润;
    3. 数组无序的,找到第一个满足[i]<[index=i+1]的,i就是我们的买入点,index就先设置为卖的点;
    4. 然后从index往后遍历,只要是找到小于index这个数组值的数的坐标就可以作为下一个买入点,到这里先计算第一个买入点和卖出点的利润,然后将下一个买入点坐标赋值给i,这样就变成了3步骤的重复,后面如果是从小到大或者大到小又变成计算1,2步骤
    5. 利润profit从每个步骤中都加等起来,得到最终利润

    然后看看大佬们的代码:

    1ms左右

    class Solution {
    public:
        int maxProfit(vector<int>& prices) 
        {
            if (prices.size()<2)
                return 0;
            int profit=0;
            for (int i=0;i<prices.size()-1;i++)
            {
                if (prices[i]<prices[i+1])
                    profit+=(prices[i+1]-prices[i]);
            }
            return profit;
        }
    };

      

      行吧,看到这个代码的时候我就知道我是个傻子了,算法题说白了是什么?不就是数学题吗,数学题考什么?考逻辑思维呗!!!(我根本没有抓住这题的本质QAQ)

    大佬们都是从数学上直接看这题的本质,不管我这个股票怎么买卖,只要有买入点和卖出点,那么最终的利润都可以靠后一个个大的值减去邻近的前一个小的值不断的累加起来得到。

    比如[1,7,8,9],买入1在9卖出利润8,也可以等于 9-8加8-7加7-1得到利润值,不管是[1,2,3,4,5]还是[5,4,3,2,1]或者无序的[7,1,5,3,6,4]都可以这样累加差值得到利润。

    自我反思:emmmmm.....没什么好反思的,我就是个菜鸡和傻子。。。。。。

    、旋转数组

     

    给定一个数组,将数组中的元素向右移动 个位置,其中 是非负数。

    示例 1:

    输入: [1,2,3,4,5,6,7]k = 3
    输出: [5,6,7,1,2,3,4]
    解释:
    向右旋转 1 步: [7,1,2,3,4,5,6]
    向右旋转 2 步: [6,7,1,2,3,4,5]
    向右旋转 3 步: 
    [5,6,7,1,2,3,4]
    
    

    示例 2:

    输入: [-1,-100,3,99]k = 2
    输出: [3,99,-1,-100]
    解释: 

    说明:

    • 尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。
    • 要求使用空间复杂度为 O(1) 的原地算法。

    我的代码:

    30ms左右

    class Solution {
    public:
        void rotate(vector<int>& nums, int k) {
            vector<int> temp;
            int len = nums.size();
            int pos;
            for(int i =0; i < len; i++){
                pos = (2*len + i - k) % len;
                temp.emplace_back(nums[pos]);
            }
            pos = -1;
            while(++pos<len)nums[pos] = temp[pos];
        }
    };

     大佬的代码:

    好吧这次没有大佬的代码,因为那个坐标图上12ms大佬的代码实在点不出来啊,太小了,我的鼠标移动了半天都点不出来,MMP这leetcode前端程序员该不会是个傻Z吧  天哪噜!!!!

    自我反思:题目要求用O(1)的原地算法,而我的是O(n),恩,我是个菜鸡,天哪好想看看12ms那个O(1)怎么写。。。。哪位大哥好心帮我点出来评论在我博客下面呗。。。。难受QAQ

    、存在重复

     

    给定一个整数数组,判断是否存在重复元素。

    如果任何值在数组中出现至少两次,函数返回 true。如果数组中每个元素都不相同,则返回 false。

    示例 1:

    输入: [1,2,3,1]
    输出: true

    示例 2:

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

    示例 3:

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

    我的代码:
    第一次看到这个题目的时候,第一时间想到的就是常规的两个for循环遍历。。。。。然后就超时了,果然没有这么简单。只好用map键值对的方式来完成这个题目了

    44ms左右:

    class Solution {
    public:
        bool containsDuplicate(vector<int>& nums) {
            unordered_map<int, int> map;
            if (nums.size() < 2) return false;
            for (auto nm : nums) {
                map[nm]++;
            }
            for (auto mp : map) {
                if (mp.second > 1)return true;
            }
            return false;
        }
    
    };

    36ms左右:

    class Solution {
    public:
        bool containsDuplicate(vector<int>& nums) {
            unordered_map<int,string> map;
            for(auto nm:nums){
                if(map.find(nm) != map.end()) return true;
                map.insert(pair<int,string>(nm,"1"));
            }
            return false;
        }
    
    };

      上面两种其实只是运用场景不同,

      第一种:全部存入后,再去看哪一个存入次数大于1次。适用于重复数在中间或者很后面。

      第二种:每次存入时都判断是否已经有这个数字存入了,也就是每次都查找一次。适用于重复数在比较前的场景。

      综合的话还是觉得第一种更好。

    大佬们的代码:

    20ms左右

    const static auto io_speed_up = []() {
        std::ios::sync_with_stdio(false);
        cin.tie(0);
        return 0;
    }();
    class Solution {
    public:
        bool containsDuplicate(vector<int>& nums) {
            if(nums.size()==1)return false;
            else {
                sort(nums.begin(),nums.end());
                int size=nums.size();
                for(int i=0;i<size-1;i++){
                    if(nums[i]==nums[i+1])return true;
                }
                return false;
            }
        }
    };

    看到这个代码的时候我都懵了,天哪你们还可以用标准库自带的sort排序。。。。。我服了!!!

    然后还加了一个std::ios::sync_with_stdio(false);关闭缓冲

    以及cin.tie(0);在默认的情况下cin绑定的是cout,每次执行 << 操作符的时候都要调用flush,这样会增加IO负担。可以通过tie(0)(0表示NULL)来解除cin与cout的绑定,进一步加快执行效率。这个有刷ACM的话就比较常见。

    自我反思:果然我还是太单纯了,不够狡猾!!!

    、只出现一次的数字

     

    给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

    说明:

    你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

    示例 1:

    输入: [2,2,1]
    输出: 1
    

    示例 2:

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

    我的代码:

    48ms左右

    class Solution {
    public:
        int singleNumber(vector<int>& nums) {
            int len = nums.size();
            if(len == 1) return nums[0];
            if(len == 0) return -8989;
            sort(nums.begin(),nums.end());
            for(int i = 0; i < len ; i++){
                if(i == 0){
                    if(nums[i] != nums[i + 1])return nums[i];
                } 
                else if(i == len - 1){
                    if(nums[i] != nums[i - 1])return nums[i];
                }
                else if(nums[i] != nums[i - 1] && nums[i] != nums[i+1]) return nums[i];
            }
            return -8989;
        }
    };

    这次我看到这次就想到上一题,先排序好在进行常规遍历对比,没什么好说的,大部分人没有经常刷算法题的都是我这个思维。

    看看大佬们的代码:

    20ms左右

    class Solution {
    public:
        int singleNumber(vector<int>& nums) {
            //求vector的最大值和最小值
            int l = *min_element(nums.begin(), nums.end()), h = *max_element(nums.begin(), nums.end()); 
            
            int mid, count; 
            while(l <= h){
                count = 0;
                mid = l + (h - l) / 2;
                for(int i = 0; i < nums.size(); i ++){
                    if(nums[i] <= mid)
                        count++;
                }
                if(count % 2 == 0)
                    l = mid + 1;
                else
                    h = mid - 1;
            }
            return l;
        }
    };

    这位大佬数学应用的不错啊,直接就用二分计数的方法加上类似于递归的形式,不断的确定出那个单数在哪一个值的区间,然后将其返回出来

    8ms左右

    class Solution {
    public:
        int singleNumber(vector<int>& nums) {
            //一个数异或一遍即可
            int res = 0;
            for (int i = 0; i < nums.size(); i++){
                res ^= nums[i];
            }
            return res;
        }
    };

    这位大佬就让我感觉到了,他这编程基础肯定无比的扎实,直接就用异或这个办法一次遍历得出答案。

    异或就是只有在两个比较的位不同时其结果是1,否则结果为0。两个数相同的时候就是0了,而这组数里只有一个数字是单独出现的,全部异或完一遍剩下的那个数就是结果

    自我反思:对于编程这种事,数学跟编程知识基础都是不可或缺的,像小小的位运算符有时候都会帮助我们剩下很多多余的操作。

    、两个数组的交集 II

    给定两个数组,编写一个函数来计算它们的交集。

    示例 1:

    输入: nums1 = [1,2,2,1], nums2 = [2,2]
    输出: [2,2]
    

    示例 2:

    输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
    输出: [4,9]

    说明:

    • 输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致。
    • 我们可以不考虑输出结果的顺序。

    进阶:

    • 如果给定的数组已经排好序呢?你将如何优化你的算法?
    • 如果 nums1 的大小比 nums2 小很多,哪种方法更优?
    • 如果 nums2 的元素存储在磁盘上,磁盘内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?

    我的代码:

    16ms左右:

    class Solution {
    public:
        vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
            vector<int> copy_nums1;
            vector<int> copy_nums2;
            vector<int> res;
            int len1 = nums1.size();
            int len2 = nums2.size();
            if(len1 == 0 || len2 == 0) return res;
            copy_nums1.assign(nums1.begin(),nums1.end());
            copy_nums2.assign(nums2.begin(),nums2.end());
            for(int i = 0; i < len1; i++){
                for(int j = 0; j < len2; j++){
                    if(copy_nums1[i] == copy_nums2[j]){
                        res.emplace_back(copy_nums1[i]);
                        copy_nums2.erase(copy_nums2.begin()+j);
                        len2--;
                        break;
                    }
                }
            }
            return res;
        }
    };

      刚开始看到这个题目的时候我只想出了两个办法,第一个就是上面这个---复制两个数组之后可以判断两个数组,小的那个放在外循环,当然这里没有加这个判断,因为那是进阶的内容了。

      一个外循环数组第二个数组里面查找,找到之后存入res,为了避免二次利用那个数,直接将第二个数组数(反正也是拷贝的数组,可以直接在上面进行操作)删除,最后返回的就是两个数组的交集了

    大佬们的代码:

    8ms左右:

    class Solution {
    public:
        vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
            unordered_map<int,int>m;
            vector<int>res;
            for(auto a:nums1)++m[a];
            for(auto a:nums2)
            {
                if(m[a]-->0)res.push_back(a);
            }
            return res;
        }
    };

    这就是我第二个想法,用map来存储,不过呢,他是用unordered_map底层是hash,查找方面速度会有一些优势。

    大佬们代码:

    1ms左右:

    class Solution {
    public:
        vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
           vector<int>res;
            sort(nums1.begin(),nums1.end());
            sort(nums2.begin(),nums2.end());
            int i=0,j=0;
            while(i<nums1.size()&&j<nums2.size())
            {
                if(nums1[i]==nums2[j])
                {
                    res.push_back(nums1[i]);
                    ++i;++j;
                }
                else if(nums1[i]<nums2[j])
                {
                    i++;
                }
                else
                    j++;
            }
            return res;
        }
    };

      这个就是进阶里面将数组排序之后在去寻找两个数组的交集,这里两个数组排序完之后没有什么好探讨的,就是使用类似双指针的形式去不断遍历两个数组找到交集存储

    自我反思:进阶里面的三没有怎么去思考,不过我想无非就是分段读取吧。

    、加一

    给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。

    最高位数字存放在数组的首位, 数组中每个元素只存储一个数字。

    你可以假设除了整数 0 之外,这个整数不会以零开头。

    示例 1:

    输入: [1,2,3]
    输出: [1,2,4]
    解释:

    示例 2:

    输入: [4,3,2,1]
    输出: [4,3,2,2]
    解释:

    我的代码:

    8ms左右:

    class Solution {
    public:
        vector<int> plusOne(vector<int>& digits) {
        const int size = digits.size();
        for (int i = size - 1; i >= 0; i--){
            digits[i]++;
            digits[i] %= 10;
            if (digits[i] != 0) return digits;
        }
        reverse(digits.begin(), digits.end());
        digits.emplace_back(1);
        reverse(digits.begin(), digits.end());
        return digits;
    }
    };

      这题无非就是两种情况一种是末尾加一不会大于10的,就直接末尾加一返回数组就行了。

    还有一种就是999,或者9999这种全是9的情况,加一的话要不断的进位,那么问题就转化成了如何在这个数组前面加一个1上去。

    所以我这里用逆序补1后在逆序回来。

    大佬们的代码:

    1ms左右:

    class Solution {
    public:
        vector<int> plusOne(vector<int>& digits) {
        const int size = digits.size();
        for (int i = size - 1; i >= 0; i--)
        {
            digits[i]++;
            digits[i] %= 10;
            if (digits[i] != 0) return digits;
        }
        digits.resize(size + 1);
        digits[0] = 1;
        return digits;
        }
    };

      大佬们比我分析的好,我没有想到这题数组值全是0的情况恰好可以直接使用resize来改变内存,resize改变后的数组默认全部初始化为0,恰好这题就是,只要把第一个0改成1就行了。

    自我反思:对STL容器的各种方法和使用场景还没有很好的掌握。

    、移动零

     

    给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

    示例:

    输入: [0,1,0,3,12]
    输出: [1,3,12,0,0]

    说明:

    1. 必须在原数组上操作,不能拷贝额外的数组。
    2. 尽量减少操作次数。

    我的代码:

    56ms左右

    class Solution {
    public:
        void moveZeroes(vector<int>& nums) {
            int len = nums.size();
            for(int i = 0; i < len; i++){
                if(nums[i] == 0){
                    nums.erase(nums.begin() + i);
                    nums.emplace_back(0);
                    len--;
                    i--;
                }
            } 
        }
    };

      我的思路就是遇到0就将其删除然后补充在最后,这样的逻辑虽然简单,但是依靠vector函数来操作的话内部其实要做许许多多的事,导致时间很慢。

    大佬们的代码:

    16ms左右:

    class Solution {
    public:
        void moveZeroes(vector<int>& nums) {
            int j = 0;//快慢指针
            for(int i = 0;i < nums.size();i++){
               while(nums[j] != 0 && j < nums.size()-1){
                        j++;
                    }
                if(nums[i] != 0 && i > j){
                    nums[j] = nums[i];
                    nums[i] = 0;
                }
                
            }
        }
    };

      大佬的思路就是快慢指针,一个指针也就是for循环正常遍历,而另外一个指针就不断的先找到0的位置停止下来,等到for循环到了0后面的位置并且是有效值,就将他们交换。

      说白了就是将0后最近的非0数与其交换,不过这样的换法,从全局上看就是不断的把每个0往末尾移动,移动的次数非常多。

    8ms左右:

    class Solution {
    public:
        void moveZeroes(vector<int>& nums) {
            int len = nums.size();
            int i = -1,j = 0;
            while(j < len)
            {
                if(nums[j] != 0)
                {
                    i += 1;
                    nums[i] = nums[j];
                }
                j += 1;
            }
            i += 1;
            while(i < len)
            {
                nums[i] = 0;
                i++;
            }
        }
    };

      这个代码就跟16ms那个代码不一样的思路,不是将0不断的往后搬了。而是将有效的数字都提到前面来,然后用i记住有效数字的最后一位在哪,之后空间的全部一次性置为0。

     、两数之和

    给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

    你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。

    示例:

    给定 nums = [2, 7, 11, 15], target = 9
    
    因为 nums[0] + nums[1] = 2 + 7 = 9
    所以返回 [0, 1]
    

    我的代码:

    两种方法,一种常规遍历,一种hash存储

    44ms左右

    class Solution {
    public:
        vector<int> twoSum(vector<int>& nums, int target) {
            int len = nums.size();
            vector<int> res;
            for(int i = 0; i < len; i++){
                for(int j = i + 1; j < len; j++){
                    if(nums[i] + nums[j] == target){
                        res.emplace_back(i);
                        res.emplace_back(j);
                        return res;
                    }
                }
            }
            return res;
        }
    };

      上面这种就是常规的遍历,没什么好说的。

    4ms左右

    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> m;
        vector<int> temp;
        int len = nums.size();
        for (int i = 0; i < len; ++i) {
            m[nums[i]] = i;
        }
        for (int i = 0; i < len; ++i) {
            if (m.count(target - nums[i]) && m[target - nums[i]] != i) {
                temp.emplace_back(i);
                temp.emplace_back(m[target - nums[i]]);
                return tem;
            }
    
        }
        return tem;
    }

      这里还是用unordered_map容器来存储查找,因为要返回的是下标。所以先将所有的值作为键,下标作为值存入容器。然后就是遍历,因为两数之和为target,那么已经知道一个值为Nums[i],那另外一个值就是target - nums[i]。

        m[target - nums[i]] != i 加上这句就是为了防止自己找到自己 比如 【3,2,7】target = 6 ,这里的话不加判断就会返回自己3的下标两次【0,0】

      然后这里使用unodered_map而不是map,因为本题不需要存入有序序列,并且本题有频繁的查找工作,unodered_map底层为hash查找比红黑树更快。

     、有效的数独

    判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。

    1. 数字 1-9 在每一行只能出现一次。
    2. 数字 1-9 在每一列只能出现一次。
    3. 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。

    上图是一个部分填充的有效的数独。

    数独部分空格内已填入了数字,空白格用 '.' 表示。

    示例 1:

    输入:
    [
      ["5","3",".",".","7",".",".",".","."],
      ["6",".",".","1","9","5",".",".","."],
      [".","9","8",".",".",".",".","6","."],
      ["8",".",".",".","6",".",".",".","3"],
      ["4",".",".","8",".","3",".",".","1"],
      ["7",".",".",".","2",".",".",".","6"],
      [".","6",".",".",".",".","2","8","."],
      [".",".",".","4","1","9",".",".","5"],
      [".",".",".",".","8",".",".","7","9"]
    ]
    输出: true
    

    示例 2:

    输入:
    [
      ["8","3",".",".","7",".",".",".","."],
      ["6",".",".","1","9","5",".",".","."],
      [".","9","8",".",".",".",".","6","."],
      ["8",".",".",".","6",".",".",".","3"],
      ["4",".",".","8",".","3",".",".","1"],
      ["7",".",".",".","2",".",".",".","6"],
      [".","6",".",".",".",".","2","8","."],
      [".",".",".","4","1","9",".",".","5"],
      [".",".",".",".","8",".",".","7","9"]
    ]
    输出: false
    解释: 除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。
         但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。

    说明:

    • 一个有效的数独(部分已被填充)不一定是可解的。
    • 只需要根据以上规则,验证已经填入的数字是否有效即可。
    • 给定数独序列只包含数字 1-9 和字符 '.' 。
    • 给定数独永远是 9x9 形式的。

    我的代码:

    32ms左右

    class Solution {
    public:
        bool containsDuplicate(vector<char>& nums) {
        if (nums.size() < 2) return false;
        unordered_map<char, int> map;
        for (auto nm : nums) {
            map[nm]++;
        }
        for (auto mp : map) {
            if (mp.second > 1)return true;
        }
        return false;
    }
    
    bool isValidSudoku(vector<vector<char>>& board) {
        const int bufsize = 2;
        vector<vector<char> > raw_col = { { '0' },{ '0' } };
        vector<vector<char> > three_temp = { { '0' },{ '0' },{ '0' } };
    
        for (int i = 0; i < board.size(); i++) {
            for (int j = 0; j < board[0].size(); j++) {
                if (board[i][j] != '.') {
                    raw_col[0].emplace_back(board[i][j]);
    
                    if (j <3) three_temp[0].emplace_back(board[i][j]);
                    else if (j < 6) three_temp[1].emplace_back(board[i][j]);
                    else three_temp[2].emplace_back(board[i][j]);
                }
    
            }
            if (containsDuplicate(raw_col[0])) return false;
            raw_col[0].clear();
            if ((i + 1) % 3 == 0) {
                for (int k = 0; k < three_temp.size(); k++){
                        if (containsDuplicate(three_temp[k])) return false;
                        three_temp[k].clear();
                }
            }
        }
        for (int j = 0; j < board[0].size(); j++) {
            for (int i = 0; i < board.size(); i++) {
                if (board[i][j] != '.') raw_col[1].emplace_back(board[i][j]);
            }
            if (containsDuplicate(raw_col[1])) return false;
            raw_col[1].clear();
        }
        return true;
    
    }
    };

      我的思路很简单,就是分别计算每一行每一列是否有重复,然后对于,判断每个九宫格是否有重复我则是采用一个二维数组来存储,看图可以划分为9个九宫格,我每次存储横向的三个九宫格然后进行判断里面是否出现重复。

      然后跳到下三个个横向九宫格的时候,将数组清空在次存入判断

    大佬们的代码:

    12ms左右

    class Solution {
    public:
        bool isValidSudoku(vector<vector<char>>& board) {
            int col[10][10], line[10][10], sq[10][10][10];
            
            memset(col,0,sizeof(col));
            memset(line,0,sizeof(line));
            memset(sq,0,sizeof(sq));
            
            for(int i=0;i<9;i++){//i -> line
                for(int j=0;j<9;j++){ // j->col
                    if(board[i][j]=='.') continue;
                    int val=board[i][j]-'0';
                    // if(val<=0 || val>9) return false;
                    if(col[j][val] || line[i][val] || sq[i/3][j/3][val]) return false;
                    col[j][val]=line[i][val]=sq[i/3][j/3][val]=1;
                }
            }
            return true;
        }
    };

      大佬则是采用创建三个标记空间的形式,把所有的数据用点亮标记的形式来判断,比如if(col[j][val])就是判断j列里val是否已经点亮为1,如果已经为1那么说明这列里面已经存在一个val值,那么就重复了。行也是这样类推。

      然后就是判断九个九宫格内是否重复,也是将九宫格划分好,比如sq[i/3][j/3][val]=1;这就是第i行的第j个九宫格中的val元素点亮置为1.。i除以3是因为i值为0-3之间就是第一行的某个九宫格,3-6则是第二行的某个九宫格。。。。j的意思也这样理解

    4ms左右

    class Solution
    {
    public:
        bool isValidSudoku(vector<vector<char>> &board)
        {
            char k;
            int num;
            bitset<9> hang;
            vector<bitset<9>> lie(9, hang), box(9, hang);
            for (int i = 0; i < 9; ++i) //
            {
                for (int j = 0; j < 9; ++j) //
                {
                    k = board[i][j];
                    if (k != '.')
                    {
                        num = k - '0' - 1;
                        if (hang[num] && lie[j][num] && box[i / 3 * 3 + j / 3][num])
                            return 0;else
                  hang[num] = lie[j][num] = box[i / 3 * 3 + j / 3][num] = 1;
                    }
                }
                hang.reset();
            }
            return 1;
        }
    };

      这个大佬应该是有刷过ACM之类的,跟上一个12ms的思路其实差不多,就是对内存各方面做了优化,用bitset来存储数据,bitset就像一个bool类型的数组一样,但是有空间优化——bitset中的一个元素一般只占1 bit,相当于一个char元素所占空间的八分之一。

    十一、旋转图像

    给定一个 × n 的二维矩阵表示一个图像。

    将图像顺时针旋转 90 度。

    说明:

    你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。

    示例 1:

    给定 matrix = 
    [
      [1,2,3],
      [4,5,6],
      [7,8,9]
    ],
    
    原地旋转输入矩阵,使其变为:
    [
      [7,4,1],
      [8,5,2],
      [9,6,3]
    ]
    

    示例 2:

    给定 matrix =
    [
      [ 5, 1, 9,11],
      [ 2, 4, 8,10],
      [13, 3, 6, 7],
      [15,14,12,16]
    ], 
    
    原地旋转输入矩阵,使其变为:
    [
      [15,13, 2, 5],
      [14, 3, 4, 1],
      [12, 6, 8, 9],
      [16, 7,10,11]
    ]
    
    8ms左右:
    class Solution {
    public:
        void rotate(vector<vector<int>>& matrix) {
            int length = matrix.size(); // 调换对角元素 
            for (int i = 0; i < length; i++) {
                for (int j = 0; j < length - i; j++) {
                    int tmp = matrix[i][j];
                    matrix[i][j] = matrix[length - j - 1][length - i - 1];
                    matrix[length - j - 1][length - i - 1] = tmp;
                }
            } // 调换列元素 
            for (int i = 0; i < length; i++) {
                for (int j = 0; j < length / 2; j++) {
                    int tmp = matrix[j][i];
                    matrix[j][i] = matrix[length - j - 1][i];//这里用异或来调换两个元素也行,自己喜欢
                    matrix[length - j - 1][i] = tmp;
                }
            }
        }
    };

    本来是想直接用图像旋转的原理公式,然是因为题目要求了只能在原数组中做旋转操作,那么就只能看看在元素调换的方面有没有什么规律。

    调换所有(/)左下到右上对角线的对角元素

    之前:

    [
      [1,2,3],
      [4,5,6],
      [7,8,9]
    ],


    之后:

    [
      [9,6,3],
      [8,5,2],
      [7,4,1]
    ],


    逆序所有列元素
    之前:

    [
      [9,6,3],
      [8,5,2],
      [7,4,1]
    ],


    之后:

    [
      [7,4,1],
      [8,5,2],
      [9,6,3]
    ],

    同理如果要调换所有()左上到右下对角线的对角元素,那么就要逆序所有行元素



    也可以看看我的其他面试题总结:

    c++面试题中经常被面试官面试的小问题总结(一)(本篇偏向基础知识)

    后面还会继续出LeetCode字符串篇以及一些其他教程,想继续看的话关注在下吧hhhhhh

     

    若有兴趣交流分享技术,可关注本人公众号,里面会不定期的分享各种编程教程,和共享源码,诸如研究分享关于c/c++,python,前端,后端,opencv,halcon,opengl,机器学习深度学习之类有关于基础编程,图像处理和机器视觉开发的知识

    
    

    
    
  • 相关阅读:
    python使用pip离线安装库
    centos 6.5 ftp服务配置及客户端使用
    centos7 ntp服务器配置
    Python:如何排序(sort)
    python报OperationalError: (1366, "Incorrect string value..."的问题解决
    使用matplotlib画双纵轴坐标
    python安装PIL库
    【干货分享】前端面试知识点锦集04(Others篇)——附答案
    【干货分享】前端面试知识点锦集03(JavaScript篇)——附答案
    【干货分享】前端面试知识点锦集02(CSS篇)——附答案
  • 原文地址:https://www.cnblogs.com/DOMLX/p/10940636.html
Copyright © 2020-2023  润新知