array专题8

670 Maximum Swap

思路:先把整数分解成一个一个的数,从0-n放着从最低位到最高位的数字。例如2376变成数组[6,7,3,2]。假设要替换的是最高位n-1,从0到n-2中查找是否有比nums[n-1]大的元素;如果有则替换,否则继续考虑替换n-2位。比较的时候一定要从0位开始,因为如果 第1,2下标元素相同,把第1位的元素换到高位数字更大。样例数据1993,替换以后是9913。
学习:首先,记录0-9这十个数字在num中的最低位数。其次,遍历每个位置,找一找从9到digits[i]+1这几个数字中是不是存在可以替换的数值。
代码

289 Game of Life

思路:这个题目的难点是既要根据数组情况修改board[i][j]的新状态,还要能作为邻居找到board[i][j]原来的状态。可以使用如下策略:
live->die 2
live->live 1
die->die 0
die->live 3
最后再遍历一次,把所有值%2。
代码

792 Number of Matching Subsequences

思路:暴力方法不再叙述。
学习:把words的第一个字符以 < c h a r a c t e r , s t r i n g > 的数据结构存储,接着遍历S中的每一个字符,找到在该字符下的words,删除words的字符,更新word。当word的长度为1时,表示匹配成功,计数。这里使用双向列表是一个技巧之处。(参考
学习2:第二种思路是:s的长度为n,计算每个位置i之后 [a-z]首次出现的位置。这样,当检查每一个word的时候,只要查看位置就可以。
例如 s=”abcde”。
指针在位置0(也就是a前面),那么a、b、c、d、e首次出现的位置分别为1,2,3,4,5。
在遇到’a’以后,a、b、c、d、e首次出现的位置分别为-1,2,3,4,5,如此循环下去。
在匹配单词bb的时候,首先指针在0,b的位置是2。指针在2,b的位置是-1.所以找不到。
代码

11 Container With Most Water

思路:根据题意,我们知道取两条边中最小的值*底长得到面积,找到最大面积。用暴力枚举是可以算得的,但会超时。
学习:减少循环的核心思路是省去没有必要的遍历,并且确保所需的答案一定能被遍历到。
算法描述是:假设现在有一个容器,则容器的盛水量是由容器的底和高共同决定的。则我们可以从最大的底入手。设两个指针一个在最右边,一个在最左边。接着移动较小值的指针,左指针右移,右指针左移。直到两指针相遇。遍历结束。计算出最大值。
算法证明:首先解释为什么移动较小值的指着。假设left值小于right值。如果移动right,底肯定会变小,如果移动之后:
1 新的right < left,那么高也变小,容量变小;
2 新的right >= left,那么高不变,底变小,容量变小。
所以移动值比较大的指针没有意义。(ps:移动值小的指针,可以认为我们是在枚举高度)。
其次证明这种移动方式肯定会经过容量最大的点。假设容量最大的时候指针分别为p_left和p_right。根据题设,遍历的时候左指针先到达p_left,右指针还没有到达p_right,假设右指针在p_right+n。这个时候会移动的条件是 p _ l e f t < ( p _ r i g h t + n ) 。如果是这样最大面积就不是p_left,p_right两指针。因为 p _ l e f t ( [ p _ r i g h t + n ] p _ l e f t ) > p _ l e f t ( p _ r i g h t p _ l e f t ) 。这与假设矛盾。所以一定会经过容量最大的点。
学习2:不但要能想到算法描述,更需要证明算法正确。

参考
代码

713 Subarray Product Less Than K

思路:首先肯定想到暴力枚举,超时。接着考虑有哪些计算是重复的。以nums=[10, 5, 2,6]为例。
计算i=0,遍历j=0,1,2,3;计算i=1,遍历j=1,2,3;如果[0,2]符合要求,那么[1,2]一定是符合要求的,进一步考虑[0],[0,1],[0,2],[1,2]都是符合要求的。当然我思考到这里就进行不下去了。
学习:slide window的思想。
10:count+1
10*5<100:count+2,因为[5],[10,5]
10*5*2=100:count+2 因为:[2],[5,2]
5*2*6<100:count+3 因为[6][6,2][6,2,5]
需要保持最大的乘积 < k的窗口;
每次向右移动一个数值nums[j];如果右移后乘积 >= k,就增加i,减少左边的数值,直到乘积<k;
每一步都会新增(j-i+1)个子数组。
这种滑动窗口,或者说每一步增加一个元素,考虑是否改变数组起止位置的思想与795 Number of Subarrays with Bounded Maximum很类似。如果能从一步计算中推出多个结论,就可以减少计算次数。降低复杂度。
代码

33 Search in Rotated Sorted Array

思路:这是要在一个有旋转的排序数组中查找。
学习:一种思路是找到旋转点的位置每次找mid,找到的是(mid+ratedIdx)%n的位置
学习2:使用最大值、最小值使得数组有序。我理解的不太好。查看网页。文中提到的If nums[mid] and target are “on the same side” of nums[0], we just take nums[mid].这句话意思是: nums[0]<nums[mid] && nums[0]<target 或者 nums[0]>nums[mid] && nums[0]>target
代码
学习3:因为rotate的缘故,当我们切取一半的时候可能会出现误区,所以我们要做进一步的判断。具体来说,假设数组是A,每次左边缘为l,右边缘为r,还有中间位置是m。在每次迭代中,分三种情况:
(1)如果 t a r g e t == A [ m ] ,那么m就是我们要的结果,直接返回;
(2)如果 A [ m ] < A [ r ] ,那么说明从m到r一定是有序的(没有受到rotate的影响),那么我们只需要判断target是不是在m到r之间,如果是则把左边缘移到m+1,否则就target在另一半,即把右边缘移到m-1。
(3)如果 A [ m ] >= A [ r ] ,那么说明从l到m一定是有序的,同样只需要判断target是否在这个范围内,相应的移动边缘即可。
根据以上方法,每次我们都可以切掉一半的数据,所以算法的时间复杂度是O(logn),空间复杂度是O(1)。(原文链接)

81 Search in Rotated Sorted Array II

学习:本题和上一题唯一的区别是这道题目中元素会有重复的情况出现。不过正是因为这个条件的出现,出现了比较复杂的case,甚至影响到了算法的时间复杂度。原来我们是依靠中间和边缘元素的大小关系,来判断哪一半是不受rotate影响,仍然有序的。而现在因为重复的出现,如果我们遇到中间和边缘相等的情况,我们就丢失了哪边有序的信息,因为哪边都有可能是有序的结果。假设原数组是{1,2,3,3,3,3,3},那么旋转之后有可能是{3,3,3,3,3,1,2},或者{3,1,2,3,3,3,3},这样的我们判断左边缘和中心的时候都是3,如果我们要寻找1或者2,我们并不知道应该跳向哪一半。解决的办法只能是对边缘移动一步,直到边缘和中间不在相等或者相遇,这就导致了会有不能切去一半的可能。所以最坏情况(比如全部都是一个元素,或者只有一个元素不同于其他元素,而他就在最后一个)就会出现每次移动一步,总共是n步,算法的时间复杂度变成O(n)。(来自网页
代码

209 Minimum Size Subarray Sum

思路:这道题目与713很类似。一个是求子数组的和,一个是求子数组的乘积。都可以使用滑动窗口的思想。这里要求得到的是子数组和 >= s 的最小长度。两指针,一个指向窗口的开始位置,一个指向窗口的结束位置。(参考网页
学习:二分查找。输入的数组没有顺序,而且也不能排序,那怎么用二分查找呢?sum[i]表示从下标0到i-1区间元素的和。因为输入的元素是正整数,所以sum[i]是一个递增函数。最短的数组长度,也就是找sum[i,j]的和。 s u m [ i , j ] = s u m [ 0 , j ] s u m [ 0 , i 1 ] >= s 。对于每个sum[0,i-1]找到 > s u m [ 0 , i 1 ] + s 的最小的值,更新数组长度。
代码

228 Summary Ranges

思路:这道题目简单。只要判断前后元素差距不等于1,就断开。需要注意的是[2147483648,-2147483647,2147483647]这样的数组。
代码

56 Merge Intervals

思路:合并。用每一个Interval和其他所有Interval去比较合并。
学习:改进思路是按照Interval的start升序排列。这样就不需要和每个Interval比较。
代码

16 3Sum Closest

思路:找三个数的和最近接一个目标值。首先想到三个数的和可能大于,也可能小于目标值。其次,枚举其中某一个值,然后使用两指针从头、尾开始相加。当然数组要求是排序了的。
代码

55 Jump Game

思路:初看是一道非常简单的题目。但是没有正确理解题意。第一点:每一个nums[i]表示最多能跳多少个元素,而不是必须跳那么多个元素;第二点:如果能够到达最后一个元素位置就返回true。有时候按照最远的跳,可能超过最后一个元素了,也是可以的。当然第一点理解正确了,第二点也就正确的。第三:如果输入[0],那么初始化的时候就已经到了最后一个元素的位置了。我按照递归的代码实现了一版,超时。

    public boolean canJump(int[] nums) {
        return jump(nums,nums.length-1);
    }
    private boolean jump(int[] nums,int idx){
        if(idx<0){
            return false;
        }
        if(idx==0){
            return nums[idx]>0 || nums.length==1;
        }
        for(int i=idx-1;i>=0;i--){
            if(nums[i]>=idx-i){
                if(jump(nums,i)){
                    return true;
                }
            }
        }
        return false;
    }

思路2:用数组表示是否能够到达i的结果保存起来,用dp[i]表示。发生栈溢出。递归改为迭代?失败,没有完成。
学习1:基本事项是:在每一步,记录下当前能够达到的最远的下标。我们循环遍历每一个下标,如果发现当前下标是不可到达的,也就是说(当前idx>reachableIdx),那就退出循环,返回false。
学习2:参考网页

169 Majority Element

思路:最开始的思路,一定是用个map记录每个元素出现次数。最后返回次数最多的那个元素。
学习:因为主元素的出现次数比其他所有元素出现次数的总和还要多1,或者相同,那么就用元素相同次数加1,元素不同次数减1,来过滤掉最后剩下的元素。Moore voting algorithm摩尔投票算法。
学习2:排序后,直接返回nums[nums.length/2]位置的元素。
学习3:位运算。一个元素的二进制表示是相同的。所有元素,计算二进制位置每个位置出现次数。
代码

229 Majority Element II

学习:229是169的升级版,摩尔投票算法的一般情况
代码

猜你喜欢

转载自blog.csdn.net/flying_all/article/details/79777219
今日推荐