美文网首页
Leetcode之数组篇

Leetcode之数组篇

作者: HugiFish | 来源:发表于2019-06-13 11:21 被阅读0次

    1.从排序数组中删除重复项

    给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
    不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
    不需要考虑数组中超出新长度后面的元素。

    解题思路:由于数组是有序的,所以只需要利用快慢指针对比新旧数组的尾部元素,如果旧数组和新数组的尾部元素则舍弃新数组尾部元素。

    int removeDuplicate(int nums,int size)
    {
        if ( size <= 0) return 0;
        int len = 0;//新数组长度
        for(int i =1;i<size;i++)//i 是旧数组尾部遍历游标
        {
            if(nums[len] != nums[i])
            {//如果新旧数组尾部元素不一致,证明此元素是需要添加到新元素尾部的
                nums[++len] = nums[i];
            }
            //如果新旧尾部元素是一样的,证明旧的尾部元素需要被舍弃掉
        }
        return len+1;//len只是新数组尾部指针,返回的新数组长度就需要+1;
    }
    

    2. 买卖股票的最佳时机 II

    解题思路:只要将所有波谷到波峰的值全部加起来就是最大的收益,并且波峰之后必定是波谷,波谷买进波峰卖出,从而达到收益最大化。波峰与波谷之间可能是两点之间也可能是多点之间,但是波峰到波谷中间所有段(连续两点之间的价格差)必定全部是下降状态,相反,波谷到波峰中间所有段必定是上升状态,从而演化成,求两点之间正收益的和。

    int maxProfit(int* prices, int pricesSize){
        if(1 >= pricesSize)//避免价值区间数组输入有误,保证程序的健壮性
            return 0;
        int sums = 0;//正收益的和
        for (int i = 1; i< pricesSize ; i++)//i为下一值的游标,用于判断此区段升降状态
        {
            if (prices[i-1] < prices[i])//正收益状态,计算收益
                sums += prices[i] -prices[i-1];
           //如果是负收益状态,那么当前点即为卖出点,当前点和下一点的负收益不记录在收益内。
        }
        return sums;
    }
    

    3. 旋转数组

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

    解题思路:利用三次逆序法,向右移动k个单位,实际上就是数组分成两个部分 [0,size-k-1][size-k,size-1],两部分前后颠倒,实际上就是全数组前后颠倒,颠倒后,再将两部分内部数组的顺序进行还原。可以得到最终的结果。值得注意的是k的数值需要对size取模,因为k=0 和k = size*n 都相当于数组没有移动。

    void reverse (int *nums,int startIndex,int len)
    {
        int i = 0;
        int endIndex = startIndex + len - 1;
        while(i < len)
        {
           if( startIndex + i < endIndex -i)
           {//利用异或运算对调两个数
                nums[startIndex + i ] = nums[startIndex + i ] ^ nums[endIndex -i];
               nums[endIndex -i] = nums[endIndex -i] ^ nums[startIndex + i ] ;
              nums[startIndex + i ] = nums[endIndex -i] ^ nums[startIndex + i ] ;
           }
           else//当startIndex +i >= endIndex -i 时,前后两部分已经对调完毕
               break;
           i++;
       }
    }
    void rotate(int *nums,int numsSize,int k)
    {
         if (0 >= numsSize ) return;//保证输入数组正确
         int realK = k % numsSize;
         if (realK == 0 ) return ;//原地没动无需执行
         reverse(nums,0,numsSize);
         reverse(nums,0,realK);
         reverse(nums,realK,numsSize-realK);
    }
    

    4. 存在重复

    给定一个整数数组,判断是否存在重复元素。
    如果任何值在数组中出现至少两次,函数返回 true。如果数组中每个元素都不相同,则返回 false。

    解题思路:思路一:通过创建一个哈希表,表下标和数组中的值为一一映射,然后表内值记录的是当前数组值出现的次数,当出现次数大于1时即可直接返回
    思路二:破坏原有的数组结构,排序,然后顺序遍历,如果遇到两个相等的值,则返回。

    bool containsDuplicate(int* nums, int numsSize) {
        if (numsSize == 0)
            return false;
        int min = nums[0], max = nums[0], len;
        // 确定哈希表范围。
        for (int i = 1; i < numsSize; i++) {
            if (nums[i] > max)
                max = nums[i];
            if (nums[i] < min)
                min = nums[i];
        }
        // 哈希表。
        len = max-min+1;
        int* hash = (int *)malloc(sizeof(int)*len);
        memset(hash,0 ,sizeof(int)*len);
        for (int i = 0; i < numsSize; i++) {
            // 判断元素是否已经出现过。
            int hashIndex = nums[i]-min;
            if (hash[hashIndex] == 1)
                return true;
            
            hash[hashIndex] ++;
        }
        
        return false;
    }
    
    

    5. 只出现一次的数字

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

    解题思路:主要应用到异或的交换率和结合率,也就是说,a^b^c^d = a^ (b^c)^d= a^(b^d)^c,异或实际上是逻辑概念,可以抽象为数学中交集并集的概念,可转化为图形的交并集加以证明,详细证明请看异或操作的图形证明
    ,题中给出的是只有一个元素是一个,其余的都是成对出现的,如果对注数字做异或操作,那么就是这样的,a^b^c^d^b^a^c = d^(a^a)^(b^b)^(c^c),一个数字异或自身一定为0,那么上面的那个公式最终得到的结果是d ,这样我们就找到了那个孤独的单身狗。

    int singleNumber(int* nums, int numsSize){
        if(0 >= numsSize) return -1;
        if(1 == numsSize) return nums[0];
        int result = 0;
        for (int i = 0 ;i<numsSize ;i++)
        {
            result = result ^ nums[i];
        }
        return result;
    }
    

    6.两个数组的交集 II

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

    6.1有序数组情况

    解题思路:依然可以利用扫描指针来挑选,第一个数组下标为index1,第二个数组下标为index2
    1.*index1 == *index2 ,则取出,index1++,index2++
    2.*index1 > *index2 ,index2++
    3.*index1 < *index2,index1++
    直至某一指针到达尾部为止。

    vector<int> intersect(vector<int>& nums1, vector<int>& nums2) 
    {
        int index1 = 0;
        int index2 = 0;
        vector<int> result ;
        while(index1<nums1.size() || index2<nums2.size() )
        {
             if( nums1[index1] == nums2[index2])
             {
                  result.push_back(nums1[index1]);
                  ++index2;
                  ++index1;
                  break;
             }
             if( nums1[index1] > nums2[index2])
             {
                  ++index2;
                  break;
             } 
             if( nums1[index1]< nums2[index2])
             {
                  ++index1;
                  break;
             }
        }
        return result;
    }
    

    6.2 无序数组情况

    解题思路:1.破坏数组,可以先将两个数组进行排序,然后按照6.1中的方法,挑选。2.利用一个MAP记录较短数组的每一个元素出现的值,然后再从较长数组中挑出,与MAP中存储的数据对应的值。

    vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
            vector<int> result;
            map<int,int> countMap;
            if(nums1.size() < nums2.size())//找到长度最小的数组
            {
                swap(nums1,nums2);
            }
             //记录最小数组的包含元素及对应个数
            for_each(nums2.begin(),nums2.end(),[&](int val)
                {
                   if(countMap.count(val))
                   {
                       countMap[val]++;
                   }
                   else
                   {
                       countMap.insert(map<int,int>::value_type(val,1));
                   }
                }
            );
            //在长数组中挑选重合的元素,每遇到重合的元素,就加入到结果数组中,然后对应元素的记录数字减1
            for_each(nums1.begin(),nums1.end(),[&](int val)
                {
                    if(countMap.count(val))
                    {
                        if(countMap[val]!=0)
                        {
                            result.push_back(val);
                            countMap[val]--;
                        }
                    }
                }
            );
            return result;
        }
    

    7.加一

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

    解题思路:需要考虑两点,1. 每一位的计算需要考虑低位的进位2.最高位进位,数字位数改变。
    我们从后往前遍历原数组,分为三种情况:个位,中间位,最高位;
    个位:原数+1
    中间位:原数+进位 -> 此位有进位,重新调整当前位和进位,当前位插入结果数组中,进位留高位判断处理。
    最高位:如果最高位存在进位,扩展数组
    处理后的结果数组是逆向的,然后翻转数组,即得到真正的结果数组。

    vector<int> plusOne(vector<int>& digits) {
            vector<int> result;
            if ( 0 == digits.size())
            {
                result.push_back(1);
                return result;
            }
            int original = 0;
            int carry = 0;
            for(vector<int>::reverse_iterator rit = digits.rbegin(); rit != digits.rend();rit++)
            {         
                if (rit == digits.rbegin())
                {
                    original = *rit + 1;
                }
                else
                {
                    original = *rit + carry;
                }
                
                if( 10 <= original)
                {
                    original = original - 10;
                    carry = 1;
                }
                else
                {
                    carry = 0;
                }
                result.push_back(original);
            }
            if (1 == carry) result.push_back(1);
            reverse(result.begin(),result.end());
            return result;
        }
    

    8. 移动零

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

    解题思路:很简单,顺序遍历数组,当遇到0时,记录0的个数,此后如果不是零,就将此数与最前面的0的交换位置,直至遍历结束。

    void moveZeroes(vector<int>& nums) {
            int zeroNum = 0;
            for(int i = 0 ; i< nums.size() ; i++)
            {
                if( 0 != nums[i])//此位置不是0
                {
                   if(zeroNum != 0)//如果前面有0,这将最前面的0和此位置的数交换
                   {
                       nums[i-zeroNum] = nums[i];
                       nums[i] = 0;
                   }
                   //如果前面没有0,不为零的位置不需要移动
                }
                else//此位置是0,零计数加一,然后接着遍历后边的位置
                {
                   ++zeroNum;
                }
            }
        }
    

    9. 两数之和

    给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
    你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。

    解题思路:这种关联型问题,肯定涉及到记录,涉及到记录的,并且可以实现线性查找的,基本上都会用到hash表,或者是c++中的unordered_map,先遍历一遍数组建立hash表,然后再遍历一遍数组找到配对信息。

    vector<int> twoSum(vector<int>& nums, int target) {
            vector<int> result(2);
            if( 1 >= nums.size()) return result;
            //建立hash表
            unordered_multimap<int,int> hashMap;
            for(int i = 0; i< nums.size();i++)
            {
                hashMap.insert({nums[i],i});
            }
            //遍历
           for(int i = 0; i< nums.size();i++)
           {    
                int part = target - nums[i];
                auto search  = hashMap.equal_range(part);
                for(auto it = search.first;it != search.second;it++)
                {
                    if (it->second != i)//当前并不是同一元素
                    {
                        result[0] = it->second;
                        result[1] = i;
                        return result;
                    }
                }
            }
            return result;
        }
    

    10.有效的数独

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

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

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

    解题思路:这个题遍历是免不了的,如果遍历,那么9*9的数组必然需要两层循环,遍历。但是这个题中即需要验证行还需要验证列,还需要验证宫,常规思维就是运行三次遍历,浪费时间,我们只需要考虑能不能将三次遍历融合成一次遍历呢?
    两层循环中,外层循环的逻辑意义是,检验第几个块,行验证时i表示的是第i行,列验证时i表示的是第i列,宫验证时i表示的是第几个宫,j则表示第i个块中的第j个元素,所以我们只需要在循环体中利用i,j创造每个块中的实际坐标,即可
    1.验证行时,array[i][j],i代表行,j代表列
    2.验证列时,array[j][i],i在标列,j代表行
    3.宫验证时,我们就需要找到每个宫的起始位置,然后根据每次j的加一操作,遍历一个方格,这个坐标是最难变换的
    每个方格起始位置的row和col是下边的
    row = (i/3)3
    col = (i%3)
    3
    j开始遍历一个方格时,不仅仅会影响到列,还会影响到行,所以最终公式应该是
    row = (i/3)3 + j/3
    col = (i%3)
    3 + j%3
    以上是遍历方法,那么验证方法实际上就是老一套,记录每个数值在每个块中出现的个数,如果哪个块出现了同一个数值,则直接返回

     bool isValidSudoku(vector<vector<char>>& board) {
            if( 9 != board.size()) return false;
            for(int i = 0 ;i<9;i++)
            {
                vector<int> row(9);
                vector<int> col(9);
                vector<int> cube(9,0);
                for(int j=0;j<9;j++)
                {
                    //行块验证
                    if(board[i][j] != '.')
                    {
                        int index = board[i][j] -'1';
                        if( row[index] != 0)
                        {
                            return false;
                        }
                        else
                        {
                            ++row[index];
                        }
                    }
                    //列块验证
                    if(board[j][i] != '.')
                    {
                        int index = board[j][i] -'1';
                        if( col[index] != 0)
                        {
                            return false;
                        }
                        else
                        {
                            ++col[index];
                        }
                    }
                    //根据逻辑下标计算数组实际下标
                    int rowIndex = 3*(i/3) + j/3;
                    int colIndex = j%3 +(i%3)*3;
                    //宫块验证
                    if(board[rowIndex][colIndex] != '.')
                    {
                        int index = board[rowIndex][colIndex] -'1';
                        if( cube[index] != 0)
                        {
                            return false;
                        }
                        else
                        {
                            ++cube[index];
                        }
                    }
                }
            }
            return true;
        }
    };
    

    11. 旋转图像

    给定一个 n × n 的二维矩阵表示一个图像。
    将图像顺时针旋转 90 度。
    说明:
    你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。

    解题思路:二维矩阵的旋转可以分割成层层回字型区域的旋转,每个单层回字型区域 ,即[i][j]->[i][n-1-j]->[n-1-j][n-1-j]->[n-1-j][j]这四个点围成的区域。

    void rotate(vector<vector<int>>& matrix) {
            if(0 >= matrix.size()) return;
            int n = matrix.size();
            int next = 0;
            for(int i =0 ;i< n/2;i++)
            {
                int loopN = n - i*2;
                for(int j=0 ;j<loopN-1;j++)
                {
                    //将1位置变成被交换值的临时位置
                    //只要按照顺序与其依次交换即可得到最后的交换信息
                    swap(matrix[i][i+j],matrix[i+j][i+loopN-1]);
                    swap(matrix[i][i+j],matrix[i+loopN-1][i+loopN-1-j]);
                    swap(matrix[i][i+j],matrix[i+loopN-1-j][i]);
                }
            }
    }
    

    相关文章

      网友评论

          本文标题:Leetcode之数组篇

          本文链接:https://www.haomeiwen.com/subject/vgskxctx.html