leetcode第一刷_ First Missing Positive

未排序数组,O(N)时间,常数空间,这道题让我非常清晰的感觉到算法的魅力。

先想一下如果允许用额外空间的话,我们会怎么做,对,我们会建立一个hash表,然后从头到尾的扫描数组,等等,怎么映射呢?有n个数,要找第一个消失的正正整数,那么这个消失的正整数的取值范围是什么呢?[1, n+1],之所以包含n+1是因为如果这n数正好是连续的前n个自然数。那我们就知道了,开一个长为n的哈希表,如果当前扫到得数是[1,n]之间的话,就放到数值减1的位置上,如果不是的话就跳过,然后从头扫一遍这个哈希表,第一个没被填上的位置加1的那个数,就是第一个消失的数。

难就难在常数空间上,那题目没有指明不能修改原来的数组,我们能不能就地做一个hash呢?这个要保证个条件,原来位置上的数据不能轻易覆盖,因为如果像开hash表那样直接在对应位置上填数据的话,要么覆盖,要么置换,覆盖会出错,置换会提高整个算法的复杂度。聪明的你可能已经想到了,我们其实不需要在对应的位置上填上那个数,只要保证能区分出一个数在不在他对应的位置上,因此只要正负就可以了。

原来的负数怎么处理?负数肯定不符合要求,只要让他们变成正的,又不会影响我们的结果就好,统一变成n+2是不错的选择。现在,数组中的所有数据都是正的了,如果遇到一个大于等于n+1的数,可以直接略过,他们要么是负数变来的,要么一开始就太大,不是我们考虑的范围。如果遇到一个小于n+1的数据,我们可以知道他应该在的位置(A[i]-1),只要把这个位置标记为负的,就可以知道这个数呆在了它应该的位置上了。发现我们并没有改变原来那个位置上数的数值,它可以继续发挥作用。只不过每次判断的时候要加个abs而已。

class Solution {
public:
    int firstMissingPositive(int A[], int n) {
        for(int i=0;i<n;i++){
            if(A[i]<=0)
                A[i] = n+2;
        }
        for(int i=0;i<n;i++){
            if(abs(A[i])<n+1){
                int cur = abs(A[i])-1;
                A[cur] = -abs(A[cur]);
            }
        }
        for(int i=0;i<n;i++){
            if(A[i]>0)
                return i+1;
        }
        return n+1;
    }
};

leetcode第一刷_ First Missing Positive

时间: 2024-08-24 18:09:15

leetcode第一刷_ First Missing Positive的相关文章

leetcode第一刷_ Palindrome Partitioning II

这道题还挺复杂的,回来看了好一会儿才想起当时怎么想的..上道题刚说不要打表,这道题就用了打表.. 总的思路是这样的,从后面往前面打表,最后一个位置的最小分割一定是0,那往前呢,如果当前考虑的位置是start,并且substr(s, i)是回文的,那么如果已知i+1开始的分割次数,那么start这个位置的分割应该就是start原来的和i+1开始的分割次数加1之间的最小值.DP的思想,很直接. 但是我忽略了一个另一个开销很大的地方,那就是每次判断回文的时候.这个跟单词分割还不一样,这个范围很大.最后

leetcode第一刷_ Flatten Binary Tree to Linked List

提示中说明了,改动后的链表相当于原树的前序遍历结果.前序遍历是根左右,因为要把转换后的左子树链接到根节点的右子树上,因此进入递归之后要先把节点的右子树保存下来,然后进入左子树,左子树转换后应该返回最后一个訪问的节点.这个节点的后继是根节点的转换后右子树.说起来很绕,可能看代码反而好一些. 注意一个问题是,不管如何.转换后的根节点一定要把左子树置空.要么会报错的. TreeNode* preOrder(TreeNode *root){ if(!root||(!root->left&&!

leetcode第一刷_Simplify Path

这道题的思路还是比较清晰的,用栈嘛,麻烦是麻烦在这些层次的细节上.主要有下面几个: ./和/:当前路径,遇到这种,应该将后面的文件夹或文件入栈. ../:上一层路径,遇到这种,应该做一次出栈操作,相当于返回了上一层目录. //:可以直接简化成'/'. 还有下面几个要注意的测试用例: 1. linux的路径名可以含有很多特殊字符,比如"_",".","*"等等,所以要特别注意含有"."的哪些路径名. 2. 在路径最后的..和.是

leetcode第一刷_Binary Tree Inorder Traversal

递归实现当然太简单,也用不着为了ac走这样的捷径吧..非递归实现还挺有意思的. 树的非递归遍历一定要借助栈,相当于把原来编译器做的事情显式的写出来.对于中序遍历,先要訪问最左下的节点,一定是进入循环后,不断的往左下走,走到不能走为止,这时候,能够从栈中弹出訪问的节点,相当于"左根右"过程的"根",然后应该怎么做呢?想一下中序遍历完根节点之后应该干嘛,对,是走到右子树中继续反复这个过程,可是有一点,假设这个节点不包括右子树怎么办?这样的情况下,下一个应该訪问的节点应该

leetcode第一刷_Sqrt(x)

这道题乍看下来非常简单,实际上要注意的问题非常多. 注意看给出来的函数的接口,返回的是int值,也就是计算结果是个近似值.怎样求呢?难道是从2开始往上算?直到某个值正好接近x?当然不行,肯定超时了.再仔细想一下,对了,有二分法,从最大的开始,每次计算一下平方,如果结果比x大,那么缩短上界,否则提高下界. 思想很正确,下面的问题是最大的那个值是多少?你会毫不犹豫的说出是x啊,x的平方根肯定比x小吧.好,那如果x是INT_MAX呢,你想用什么类型来存储这个平方的结果?而且这样每次减半,也得好一会儿才

leetcode第一刷_Subsets II

要求子集,有非常现成的方法.N个数,子集的个数是2^N,每个元素都有在集合中和不在集合中两种状态,这些状态用[0,pow(2,N)]中每个数来穷举,如果这个数中的第i位为1,说明当前集合中包含源数组中的第i个数. 至于有没有重复的元素,大部分有重复元素的问题,都可以借助一个vis集合,里面存放所有已经求得的集合或者其他形式的解,只有少数题目会超时,哪些问题具体的说. class Solution { public: vector<vector<int> > subsetsWithD

leetcode第一刷_Decode Ways

这道题还挺难的.递归的思路是好想,不过不出意料的超时了. dp嘛.想一下i-1的编码加上第i个编码会怎样,如果加上的这个编码不是0,那么这一位可以独立解码,那长为i的解码个数至少是长为i-1的解码个数.还有呢?如果i-1位是1,可以把i-1位和i位同时解码出来,还有呢?如果i-1位是2而i位是0-6中的数字,也可以同时解码这两位编码.满足这个条件的时候,当前长度的解码个数还要加上i-2时的解码个数. 完全可以用一个数组存放过去的结果,但是很明显,当前的结论只与前一个和前前一个结果有关系.只要用三

leetcode第一刷_Gray Code

说到格雷码,应该没人不知道,具体它有什么用,我还真不是很清楚,我室友应该是专家.生成的规律不是很明显,之前看到帖子讲的,这会儿找找不到了.. 思想是这样的,如果有n位,在第2^(n-1)个编码下面画一条水平线的话,你会发现除了第一位之外,其他位都是关于这条线对称的,如下,以三位格雷码举例: 000 001 011 010 --------------------- 110 111 101 100 很神奇吧,我以前是不知道这个规律的.从一开始的一位格雷码,0,1,开始,每次对称的在上一个前面添加上

leetcode第一刷_Permutation Sequence

这道题还挺好的,如果你的思路是每次生成一个全排列,然后累计到k次,那么停下来吧,肯定超时了亲.. 微软今年的笔试题里有一道类似的,我之前已经提到过了,是只有0和1的字符串,求第k个排列是什么样子的.这道题比那个要难一些,但是总体的思路是一样的.假设有n个数要组成排列,求第k个排列.像填表一样,从高位往地位,逐个填写.先考虑有n-1个数要组成排列,最多有(n-1)!种情况,当第n个数加入后,第n个数可以是从1增加到n的,没增加1,所包含的全排列数就会增加(n-1)!,因此,如果用k/(n-1)!,