Leetcode之数组篇

2019-06-13  本文已影响0人  HugiFish

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]);
            }
        }
}
上一篇下一篇

猜你喜欢

热点阅读