leetcode算法思路总结

leetcode算法思路总结

1.Minimum Window Substring问题

Given a string S and a string T, find the minimum window in S which will contain all the characters in T in complexity O(n).

For example,
S = “ADOBECODEBANC”
T = “ABC”

Minimum window is “BANC”.

Note:
If there is no such window in S that covers all characters in T, return the emtpy string “”.

If there are multiple such windows, you are guaranteed that there will always be only one unique minimum window in S.

提示:hashtable 双指针

思路:双指针,动态维护一个区间。尾指针不断往后扫,当扫到有一个窗口包含了所有T的字符后,然后再收缩头指针,直到不能再收缩为止。用hashtable记录下T字符串中每个字符出现的次数。最后记录所有可能的情况中窗口最小的

2.计算小于n内素数的个数

Description:

Count the number of prime numbers less than a non-negative number, n.

思路:求小于n的质数的个数,题目中给了充足的提示,解题方法就在第二个提示埃拉托斯特尼筛法Sieve of Eratosthenes中,这个算法的过程:我们从2开始遍历到根号n,先找到第一个质数2,然后将其所有的倍数全部标记出来,然后到下一个质数3,标记其所有倍数,一次类推,直到根号n,此时数组中未被标记的数字就是质数。我们需要一个n-1长度的bool型数组来记录每个数字是否被标记,长度为n-1的原因是题目说是小于n的质数个数,并不包括n。

3.Copy List with Random Pointer问题

A linked list is given such that each node contains an additional random pointer which could point to any node in the list or null.

Return a deep copy of the list.

思路:不用保存原始链表的映射关系,构建新节点时,指针做如下变化,即把新节点插入到相应的旧节点后面:

同理分两步

1、构建新节点random指针:new1->random = old1->random->next, new2-random = NULL, new3-random = NULL, new4->random = old4->random->next

2、恢复原始链表以及构建新链表:例如old1->next = old1->next->next, new1->next = new1->next->next

4.寻找递增的旋转数组里面最小的数

Suppose a sorted array is rotated at some pivot unknown to you beforehand.

(i.e., 0 1 2 4 5 6 7 might become 4 5 6 7 0 1 2).

Find the minimum element.

You may assume no duplicate exists in the array.

思路:折半查找。

有序的旋转数组前半部分和后半部分都是递增的序列。数组第一个元素要大于等于最后一个元素。否则数组本身就是个递增的序列,第一个元素就是最小数

如果不是递增序列,则取中间数。

1.如果中间数大于数组的第一个元素,说明最小数存在后半段,范围缩小到中间数到最后一个元素左开右闭区间

2.如果中间数小于数组的第一个元素,说明最小数存在前半段,范围缩小到第一个元素和中间数闭区间左开右闭区间。

3.如果中间数等于数组第一个元素,需要特殊处理。

3.1 如果中间数大于数组最后一个元素,说明最小数存在后半段, 范围缩小到中间数到最后一个元素左开右闭区间

3.2 如果中间数等于数组最后一个元素,则需分别找出这第一个元素到中间数,和中间数到最后一个元素这两个区间的最小数,然后对这两个最小数进行比较,最后取最小值。

5.Word Break问题

Given a string s and a dictionary of words dict, determine if s can be segmented into a space-separated sequence of one or more dictionary words.

For example, given
s = “leetcode”,
dict = [“leet”, “code”].

Return true because “leetcode” can be segmented as “leet code”.

思路: 用动态规划方法,dp最主要的思想是找到重复子问题,推出状态方程,我们看这道题的状态方程,其实跟其他dp算法还是有点区别的,状态方程为f(i) = 存在j 使得f(j) && s[j+1,i]属于dict

6.Wildcard Matching通配符匹配问题

Implement wildcard pattern matching with support for ‘?’ and ‘*’.

‘?’ Matches any single character.
‘*’ Matches any sequence of characters (including the empty sequence).

The matching should cover the entire input string (not partial).

The function prototype should be:
bool isMatch(const char *s, const char *p)

Some examples:
isMatch(“aa”,”a”) → false
isMatch(“aa”,”aa”) → true
isMatch(“aaa”,”aa”) → false
isMatch(“aa”, “*”) → true
isMatch(“aa”, “a*”) → true
isMatch(“ab”, “?*”) → true
isMatch(“aab”, “c*a*b”) → false

思路:用动态规划,具体思路如下:

用dp[i][j]表示字符串s的前i个字符和字符串p的前j个字符是否匹配,那么状态转移情况如下:

if (dp[i-1][j-1] && (s[i] == p[j] || ‘?’ == p[j])) dp[i][j] = true, 即如果前i-1和前j-1个字符匹配,当前字符也匹配,那么前i个和前j个也匹配。

如果p的当前字符为’*’号,那么可以分两种情况:

(1) 如果dp[i-1][j-1],那么p的前j个字符和s的前k(i-1<=k<=len_s)个字符都匹配,注意这里有一个i-1,因为*可以匹配空串。

(2)如果dp[i][j-1],即s的前i个字符和字符串p的前j-1个字符,那么p的前j个字符和s的前k(i<=k<=len_s)个字符匹配,注意这里没有i-1,因为这是让*去匹配i后面的字符串。

这种解法的时间复杂度和空间复杂度都为O(N^2),所以在leetcode上只能过小数据不能过大数据。

7.Unique Binary Search Trees 二叉搜索树数目问题(还要仔细研究)

Given n, how many structurally unique BST’s (binary search trees) that store values 1…n?

For example,
Given n = 3, there are a total of 5 unique BST’s.

思路:注意:二分查找树的定义是,左子树节点均小于root,右子树节点均大于root!不要想当然地将某个点作为root时,认为其他所有节点都能全部放在left/right中,除非这个点是 min 或者 max 的。

分析:本题其实关键是递推过程的分析,n个点中每个点都可以作为root,当 i 作为root时,小于 i 的点都只能放在其左子树中,大于 i 的点只能放在右子树中,此时只需求出左、右子树各有多少种,二者相乘即为以 i 作为root时BST的总数。

开始时,我尝试用递归实现,但是超时了,可见系统对运行时间有要求。因为递归过程中存在大量的重复计算,从n一层层往下递归,故考虑类似于动态规划的思想,让底层的计算结果能够被重复利用,故用一个数组存储中间计算结果(即 1~n-1 对应的BST数目),这样只需双层循环即可。

8.Triangle问题

Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjacent numbers on the row below.

For example, given the following triangle

The minimum path sum from top to bottom is 11 (i.e., 2 + 3 + 5 + 1 = 11).

思路:,动态规划。到第i层的第k个顶点的最小路径长度表示为f(i,k),则f(i, k) = min{f(i-1,k), f(i-1,k-1)} + d(i, k); 其中d(i, k)表示原来三角形数组里的第i行第k列的元素。则可以求得从第一行到最终到第length-1行第k个元素的最小路径长度,最后再比较第length-1行中所有元素的路径长度大小,求得最小值。

9.Regular Expression Matching正则表达式匹配问题

Implement regular expression matching with support for ‘.’ and ‘*’.

‘.’ Matches any single character.
‘*’ Matches zero or more of the preceding element.

The matching should cover the entire input string (not partial).

The function prototype should be:
bool isMatch(const char *s, const char *p)

Some examples:
isMatch(“aa”,”a”) → false
isMatch(“aa”,”aa”) → true
isMatch(“aaa”,”aa”) → false
isMatch(“aa”, “a*”) → true
isMatch(“aa”, “.*”) → true
isMatch(“ab”, “.*”) → true
isMatch(“aab”, “c*a*b”) → true

思路:动态规划,动态规划基本思想就是把我们计算过的历史信息记录下来,等到要用到的时候就直接使用,不用重新计算。在这个题里面,假设我们维护一个布尔数组res[i][j],代表s的前i个字符和p的前j个字符是否匹配(注意这里res的维度是s.length()+1,p.length()+1)。递推公式跟上面类似,分三种种情况:

(1)p[j+1]不是'*'。情况比较简单,只要判断如果当前s的i和p的j上的字符一样(如果有p在j上的字符是'.',也是相同),并且res[i][j]==true,则res[i+1][j+1]也为true,res[i+1][j+1]=false; 
(2)p[j+1]是'*',但是p[j]!='.'。那么只要以下条件有一个满足即可对res[i+1][j+1]赋值为true: 
1)res[i+1][j]为真('*'只取前面字符一次); 
2)res[i+1][j-1]为真('*'前面字符一次都不取,也就是忽略这两个字符); 
3)res[i][j+1] && s[i]==s[i-1] && s[i-1]==p[j-1](这种情况是相当于i从0到s.length()扫过来,如果p[j+1]对应的字符是‘*’那就意味着接下来的串就可以依次匹配下来,如果下面的字符一直重复,并且就是‘*’前面的那个字符)。 
(3)p[j+1]是'*',并且p[j]=='.'。因为".*"可以匹配任意字符串,所以在前面的res[i+1][j-1]或者res[i+1][j]中只要有i+1是true,那么剩下的res[i+1][j+1],res[i+2][j+1],...,res[s.length()][j+1]就都是true了。 

这道题有个很重要的点,就是实现的时候外层循环应该是p,然后待匹配串s内层循环扫过来。代码如下:

 public boolean isMatch(String s, String p) {  
    if(s.length()==0 && p.length()==0)  
        return true;  
    if(p.length()==0)  
        return false;  
    boolean[][] res = new boolean[s.length()+1][p.length()+1];  
    res[0][0] = true;  
    for(int j=0;j<p.length();j++)  
    {  
        if(p.charAt(j)=='*')  
        {  
            if(j>0 && res[0][j-1]) res[0][j+1]=true;  
            if(j<1) continue;  
            if(p.charAt(j-1)!='.')  
            {  
                for(int i=0;i<s.length();i++)  
                {  
                    if(res[i+1][j] || j>0&&res[i+1][j-1]   
                    || i>0 && j>0 && res[i][j+1]&&s.charAt(i)==s.charAt(i-1)&&s.charAt(i-1)==p.charAt(j-1))  
                        res[i+1][j+1] = true;  
                }  
            }  
            else  
            {  
                int i=0;  
                while(j>0 && i<s.length() && !res[i+1][j-1] && !res[i+1][j])  
                    i++;  
                for(;i<s.length();i++)  
                {  
                    res[i+1][j+1] = true;  
                }  
            }  
        }  
        else  
        {  
            for(int i=0;i<s.length();i++)  
            {  
                if(s.charAt(i)==p.charAt(j) || p.charAt(j)=='.')  
                    res[i+1][j+1] = res[i][j];  
            }  
        }  
    }  
    return res[s.length()][p.length()];  
}  

10.Edit Distance

Given two words word1 and word2, find the minimum number of steps required to convert word1 to word2. (each operation is counted as 1 step.)

You have the following 3 operations permitted on a word:

a) Insert a character
b) Delete a character
c) Replace a character

思路:二维动态规划,如果我们用 i 表示当前字符串 A 的下标,j 表示当前字符串 B 的下标。 如果我们用d[i, j] 来表示A[1, … , i] B[1, … , j] 之间的最少编辑操作数。那么我们会有以下发现:

  1. d[0, j] = j;

  2. d[i, 0] = i;

  3. d[i, j] = d[i-1, j - 1] if A[i] == B[j]

  4. d[i, j] = min(d[i-1, j - 1], d[i, j - 1], d[i-1, j]) + 1 if A[i] != B[j]

11.Decode Ways

A message containing letters from A-Z is being encoded to numbers using the following mapping:

‘A’ -1
‘B’ -2

‘Z’ -26

Given an encoded message containing digits, determine the total number of ways to decode it.

For example,
Given encoded message “12”, it could be decoded as “AB” (1 2) or “L” (12).

The number of ways decoding “12” is 2.

思路:动态规划,考虑一般情况
1.如果S[i] == ‘0’,如果S[i-1]存在且为’1’或者’2’,F[i] = F[i-1],否则无解;
2.如果S[i] != ‘0’

如果S[i-1]==’1’,F[i] = F[i-1] + F[i-2](例如”xxxxxx11”,可以是”xxxxxx11”,也可以是”xxxxxx1” + “1“);
如果S[i-1]==’2’,当S[i] <= ‘6’时,F[i] = F[i-1] + F[i-2] (最大的Z为”26”,”27”“28”“29”不存在),当S[i] > ‘6’时,F[i] = F[i-1] (例如”xxxxxx28”,只能是”xxxxxx2” + “8”)。

12.Distinct Subsequences

Given a string S and a string T, count the number of distinct subsequences of T in S.

A subsequence of a string is a new string which is formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters. (ie, “ACE” is a subsequence of “ABCDE” while “AEC” is not).

Here is an example:
S = “rabbbit”, T = “rabbit”

Return 3.

思路:动态规划,类似于数字分解的题目。dp[i][j]表示:b的前j个字符在a的前i个字符中出现的次数。

如果a[i] == b[j]则 dp[i][j] = dp[i-1][j] + dp[i-1][j-1]

如果a[i] != b[j]则 dp[i][j] = dp[i-1][j]

13.Divide Two Integers 不用乘除取余操作求除法

Divide two integers without using multiplication, division and mod operator.

If it is overflow, return MAX_INT.

思路:题目只有简单的一句话,看起来可真简单啊,呵呵,假象。这个题目的难点在于对时间效率的限制和边界值的测试。第一印象肯定是循环一个个把因子从被除数中减去不久行了么,可是对于比如INT_MAX/1或者INT_MIN/1之类的执行时间长的可怕,会超出时间限制。改善时间效率的思路是参考网上别人代码,将因子不断乘以2(可以通过移位实现,同时结果也从1开始不断移位加倍),然后和被除数比较,等到大于被除数一半了,就从被除数中减去,将因子个数叠加入结果中。然后在剩下的被除数中采用同样的方法减去小于其一半的因子和,循环往复。

14.Trapping Rain Water

Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it is able to trap after raining.

For example,
Given [0,1,0,2,1,0,1,3,2,1,2,1], return 6.

思路:观察下就可以发现被水填满后的形状是先升后降的塔形,因此,先遍历一遍找到塔顶,然后分别从两边开始,往塔顶所在位置遍历,水位只会增高不会减小,且一直和最近遇到的最大高度持平,这样知道了实时水位,就可以边遍历边计算面积。

15.Simplify Path

Given an absolute path for a file (Unix-style), simplify it.

For example,
path = “/home/”, =”/home”
path = “/a/./b/../../c/”, =”/c”

click to show corner cases.
Corner Cases:

Did you consider the case where path = "/../"?
In this case, you should return "/".
Another corner case is the path might contain multiple slashes '/' 
together, such as "/home//foo/".
In this case, you should ignore redundant slashes and return "/home/foo".

思路:字符串处理,由于”../”是返回上级目录(如果是根目录则不处理),因此可以考虑用栈记录路径名,以便于处理。需要注意几个细节:

重复连续出现的’/’,只按1个处理,即跳过重复连续出现的’/’;

如果路径名是”./”,则不处理;

如果路径名是”../”,则需要弹栈,如果栈为空,则不做处理;

如果路径名为其他字符串,入栈。

最后,再逐个取出栈中元素(即已保存的路径名)

16.Largest Rectangle in Histogram

Given n non-negative integers representing the histogram’s bar height where the width of each bar is 1, find the area of largest rectangle in the histogram.

For example,
Given height = [2,1,5,6,2,3],
return 10.

思路:这样的题目一般都有O(n)的解法,因为O(n2)的解法实在是太显而易见了。

基本思想就是遍历所有[i, j],并在过程中找出中间最矮的bar,得出从i到j的矩形面积。

不过我就知道,一定有大神用他们极简的代码来切题,下面就是一个。
例子就用题目中的[2,1,5,6,2,3]吧。

首先,如果栈是空的,那么索引i入栈。那么第一个i=0就进去吧。注意栈内保存的是索引,不是高度。然后i++。

然后继续,当i=1的时候,发现h[i]小于了栈内的元素,于是出栈。(由此可以想到,哦,看来stack里面只存放单调递增的索引)

这时候stack为空,所以面积的计算是h[t] * i.t是刚刚弹出的stack顶元素。也就是蓝色部分的面积。

继续。这时候stack为空了,继续入栈。注意到只要是连续递增的序列,我们都要keep pushing,直到我们遇到了i=4,h[i]=2小于了栈顶的元素。

这时候开始计算矩形面积。首先弹出栈顶元素,t=3。即下图绿色部分。

接下来注意到栈顶的(索引指向的)元素还是大于当前i指向的元素,于是出栈,并继续计算面积,桃红色部分。

最后,栈顶的(索引指向的)元素大于了当前i指向的元素,循环继续,入栈并推动i前进。直到我们再次遇到下降的元素,也就是我们最后人为添加的dummy元素0.

同理,我们计算栈内的面积。由于当前i是最小元素,所以所有的栈内元素都要被弹出并参与面积计算。

注意我们在计算面积的时候已经更新过了maxArea。

总结下,我们可以看到,stack中总是保持递增的元素的索引,然后当遇到较小的元素后,依次出栈并计算栈中bar能围成的面积,直到栈中元素小于当前元素。

可是为什么这个方法是正确的呢? 我也没搞清楚。只是觉得不明觉厉了。

可以这样理解这个算法,看下图。

例如我们遇到最后遇到一个递减的bar(红色)。高度位于红线上方的(也就是算法中栈里面大于最右bar的)元素,他们是不可能和最右边的较小高度bar围成一个比大于在弹栈过程中的矩形面积了(黄色面积),因为红色的bar对他们来说是一个短板,和红色bar能围成的最大面积也就是红色的高度乘以这些“上流社会”所跨越的索引范围。但是“上流社会”的高度个个都比红色bar大,他们完全只计算彼此之间围成的面积就远远大于和红色bar围成的任意面积了。所以红色bar是不可能参与“上流社会”的bar的围城的(好悲哀)。

但是屌丝也不用泄气哦。因为虽然长度不占优势,但是团结的力量是无穷的。它还可以参与“比较远的”比它还要屌丝的bar的围城。他们的面积是有可能超过上流社会的面积的,因为距离啊!所以弹栈到比红色bar小就停止了。

另外一个细节需要注意的是,弹栈过程中面积的计算。

h[t] * (stack.isEmpty() ? i : i - stack.peek() - 1)

h[t]是刚刚弹出的栈顶端元素。此时的面积计算是h[t]和前面的“上流社会”能围成的最大面积。这时候要注意哦,栈内索引指向的元素都是比h[t]小的,如果h[t]是目前最小的,那么栈内就是空哦。而在目前栈顶元素和h[t]之间(不包括h[t]和栈顶元素),都是大于他们两者的。如下图所示:

那h[t]无疑就是Stack.Peek和t之间那些上流社会的短板啦,而它们的跨越就是i - Stack.Peek - 1。

所以说,这个弹栈的过程也是维持程序不变量的方法啊:栈内元素一定是要比当前i指向的元素小的。

代码:

public int largestRectangleArea2(int[] height) {
    Stack<Integer> stack = new Stack<Integer>();
    int i = 0;
    int maxArea = 0;
    int[] h = new int[height.length + 1];
    h = Arrays.copyOf(height, height.length + 1);
    while(i < h.length){
        if(stack.isEmpty() || h[stack.peek()] <= h[i]){
            stack.push(i++);
        }else {
            int t = stack.pop();
            maxArea = Math.max(maxArea, h[t] * (stack.isEmpty() ? i : i - stack.peek() - 1));
        }
    }
    return maxArea;
}

17.Basic Calculator 计算器实现

Implement a basic calculator to evaluate a simple expression string.

The expression string may contain open ( and closing parentheses ), the plus + or minus sign -, non-negative integers and empty spaces .

You may assume that the given expression is always valid.

Some examples:

“1 + 1” = 2
” 2-1 + 2 ” = 3
“(1+(4+5+2)-3)+(6+8)” = 23

Note: Do not use the eval built-in library function.

思路:stack 逆波兰后缀表达式

表达式求值可以分解为下列两步完成:

  1. 中缀表达式转为后缀表达式(逆波兰表达式)

  2. 后缀表达式求值

中缀表达式转为后缀表达式的要点:

开始扫描;

数字时,加入后缀表达式;

运算符:

a. 若为 ‘(‘,入栈;

b. 若为 ‘)’,则依次把栈中的的运算符加入后缀表达式中,直到出现’(‘,从栈中删除’(’ ;

c. 若为 除括号外的其他运算符, 当其优先级高于除’(‘以外的栈顶运算符时,直接入栈。否则从栈顶开始,依次弹出比当前处理的运算符优先级高和优先级相等的运算符,直到一个比它优先级低的或者遇到了一个左括号为止。
·当扫描的中缀表达式结束时,栈中的的所有运算符出栈;

后缀表达式求值的过程简述:

建立一个栈S

从左到右读表达式,如果读到操作数就将它压入栈S中

如果读到n元运算符(即需要参数个数为n的运算符)则取出由栈顶向下的n项按操作符运算,再将运算的结果代替原栈顶的n项,压入栈S中

如果后缀表达式未读完,则重复上面过程,最后输出栈顶的数值则为结束。

java代码:

 public int calculate(String s) {
        Map<Character, Integer> pmap=new HashMap<Character, Integer>();
        pmap.put('+', 1);
        pmap.put('-', 1);
        pmap.put('*', 2);
        pmap.put('/', 2);
        pmap.put('(', 0);
        pmap.put(')', -1);
        StringBuilder re=new StringBuilder("");
        Stack<Character> sta=new Stack<Character>();
        for(int i=0;i<s.length();i++){
            char a=s.charAt(i);
            if(a==' '){
                continue;
            }
            if((a-'0')>=0&&(a-'0')<10){
                re.append(a);
                while(true){
                    i++;
                    if(i>=s.length()){
                        break;
                    }
                    char cc=s.charAt(i);
                    if((cc-'0')>=0&&(cc-'0')<10){
                        re.append(cc);
                    }else{
                        i--;
                        break;
                    }
                } 
                re.append('@');
                continue;
            }
            if(sta.empty()){
                sta.push(a);
            }else{
                if(a=='('){
                    sta.push(a);
                    continue;
                }
                if(a==')'){
                    while(true){
                        char b=sta.pop();
                        if(b=='('){
                            break;
                        }else{
                            re.append(b);
                        }
                    }
                    continue;
                }
                while(true){
                    if(sta.empty()){
                        sta.push(a);
                        break;
                    }
                    char b=sta.peek();
                    if(pmap.get(b)<pmap.get(a)){
                        sta.push(a);
                        break;
                    }else{
                        re.append(sta.pop());
                    }
                }
            }
        }
        while(!sta.empty()){
            re.append(sta.pop());
        }
        System.out.println(re);
        Stack<Integer> stac=new Stack<Integer>();
        for(int i=0;i<re.length();i++){
            char a=re.charAt(i);            
            if((a-'0')>=0&&(a-'0')<10){
                StringBuilder ss=new StringBuilder("");
                ss.append(a);
                while(true){
                    i++;
                    if(i>=re.length()){
                        break;
                    }
                    char nn=re.charAt(i);
                    if((nn-'0')>=0&&(nn-'0')<10){
                        ss.append(nn);
                    }else{
                        break;
                    }
                }
                int sum=0;
                for(int k=0;k<ss.length();k++){
                    sum=sum*10+(ss.charAt(k)-'0');
                }
                stac.push(sum);
            }else{
                int aa=stac.pop();
                int bb=stac.pop();
                if(a=='+'){
                    stac.push(bb+aa);
                    continue;
                }
                if(a=='-'){
                    stac.push(bb-aa);
                    continue;
                }
                if(a=='*'){
                    stac.push(bb*aa);
                    continue;
                }
                if(a=='/'){
                    stac.push(bb/aa);
                    continue;
                }
            }
        }
        return stac.pop();
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值