leetcode刷题笔记

类型一:

17. Letter Combinations of a Phone Number (没做出来次数:1)

Given a string containing digits from 2-9 inclusive, return all possible letter combinations that the number could represent.

Example:

Input: "23"

Output: ["ad","ae","af","bd","be","bf","cd","ce","cf"]

// 遇到的问题
// 思路总是集中在用迭代的思想在一个函数周期内去解决问题,但其实返回的字母组合的数量是不能确定的,因为其总个数应该为
// map.get(index1).size()*map.get(index2).size()*...*map.get(indexN).size();
// 这样,在每一次获得一个map.get(index)对应的字符串时,需要按照串中每一个字符占总字母组合的个数的比重访问每个字母组合并添加对应字符。
// 但是,每个字符添加的位置又是不固定的,例如,a、b、c分别添加在前1/3,中1/3,末1/3的字母组合里,但 d,e,f则不按这个顺序来,而是每隔两个位置添加一次
// 当数字串变长以后,这个添加的规则会更加的复杂,这样分析,这个方法就行不通

// 解题思路
// 当迭代的方法做不到时,需要考虑递归的方法。该题的目的是使当前给出的数字经查字典后找到对应的字符串,将字符串中的任一个字符添加到前面已匹配的子字母组合中。
// 当前的字符匹配并不影响之前的字符匹配,且每个字母组合的长度是固定的,故子字母组合+当前匹配字符的长度等于字母组合的规定长度时,得到一个字母组合结果。

// 1)首先,每个数字和不同的字符有着一一对应的关系,故需用到map结构构造一个查询字典
// 原做法:在类中添加Map<String,List<String>>成员,其中,将每个英文当作字符对待,并将同一组字符添加到同一个List使其与对应数字关联, 定义Map和初始化分开
Map<String,List<String>> phone = new Map<String,List<String>();
void initialMap(Map<String,List<String>> map){
    List<String> list2 = new ArrayList<String>>();
    Collections.addAll(list2,"a","b","c");
    map.put("2",list2);
    List<String> list3 = new ArrayList<String>>();
    Colloections.addAll(list3,"d","e","f");
    map.put("3",list3);
    List<String> list4 = new ArrayList<String>>();
    Colloections.addAll(list4,"d","e","f");
    map.put("4",list4);
    List<String> list5 = new ArrayList<String>>();
    Colloections.addAll(list5,"d","e","f");
    map.put("5",list5);
    List<String> list6 = new ArrayList<String>>();
    Colloections.addAll(list6,"d","e","f");
    map.put("6",list6);
    List<String> list7 = new ArrayList<String>>();
    Colloections.addAll(list7,"d","e","f");
    map.put("7",list7);
    List<String> list8 = new ArrayList<String>>();
    Colloections.addAll(list8,"d","e","f");
    map.put("8",list8);
    List<String> list9 = new ArrayList<String>>();
    Colloections.addAll(list9,"d","e","f");
    map.put("9",list9);
}

// 更简洁的做法:在类中定义Map时初始化成员,并利用匿名内部类使代码更简洁(第一层括弧实际是定义了一个匿名内部类 (Anonymous Inner Class),第二层括弧实际上是一个实例初始化块 (instance initializer block),这个块在内部匿名类构造时被执行。)
Map<String, String> phone = new HashMap<String, String>() 
{
    {
        put("2", "abc");
        put("3", "def");
        put("4", "ghi");
        put("5", "jkl");
        put("6", "mno");
        put("7", "pqrs");
        put("8", "tuv");
        put("9", "wxyz");
    }
};

// 2)递归地在子字母组合尾部添加一个字符,到达规定长度,就添加到结果集合
public void backtrack(String combination, String next_digits) {
  // if there is no more digits to check
  if (next_digits.length() == 0) {  //到达字母字母的规定长度,将该组合添加到结果集合
    output.add(combination);
  }
    // if there are still digits to check
    else {
        String digit = next_digits.substring(0, 1);  
        String letters = phone.get(digit);           //在字典中查找当前待添加字母所在的字符串
        for (int i = 0; i < letters.length(); i++) { //在字符串任选一个字符,进行递归添加
        String letter = letters.substring(i, i + 1);
        backtrack(combination + letter, next_digits.substring(1));
        }
    }
}        
 

22. Generate Parentheses (没做出来次数:1)  (与上题类似,将上题解决后重新做了一遍该题,一下就做出来了)

Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses.

For example, given n = 3, a solution set is:

["((()))" , "(()())" , "(())()" , "()(())" , "()()()"]

//用递归的思想去解决问题
List<String> result = new ArrayList<String>();
public void backtrace(String combination,int left, int right){
    if(left==0&&right==0){
        result.add(combination);
        return;
    }
    if(left>right)return;
    if(left!=0){
        backtrace(combination+"(",left-1,right);
    }
    backtrace(combination+")",left,right-1);
}
View Code

 

39. Combination Sum (没做出来次数:1) (与上题解决方案相似,也是重做了一遍,一下就解决了)

Given a set of candidate numbers (candidates) (without duplicates) and a target number (target), find all unique combinations in candidates where the candidate numbers sums to target.

The same repeated number may be chosen from candidates unlimited number of times.

Note:

  • All numbers (including target) will be positive integers.
  • The solution set must not contain duplicate combinations.

Example :

Input: candidates = [2,3,6,7] , target = 7

A solution set is :

[

  [7] ,

  [2,2,3] 

] 

//用递归的思想去解决问题
Set<List<Integer>> set = new HashSet<List<Integer>>();
public void recursion(int[] arr, int[] candidates, int restTarget){
    if(restTarget==0){
        List<Integer> list = new ArrayList<Integer>();
        for(int i=0;i<arr.length;++i)
            list.add(arr[i]);
        Collections.sort(list);
        set.add(list);
        return;
    }
    for(int i=0;i<candidates.length;++i){
        int temp = candidates[i];
        if(temp<=restTarget){
            int new_len;
            if(arr==null) new_len=1;
            else new_len = arr.length+1;
            int[] new_arr = new int[new_len];
            for(int j=0;j<new_len-1;++j){
                new_arr[j] = arr[j];
            }
            new_arr[new_len-1] = temp;
            recursion(new_arr,candidates,restTarget-temp);
        }
    }
}
View Code

 

40. Combination Sum II (没做出来次数:1) (与上题相似,用同样的做法,一次性就做出来了)

Given a collection of candidate numbers (candidates) and a target number (target), find all unique combinations in candidates where the candidate numbers sums to target.

Each number in candidates may only be used once in the combination.

Note:

  • All numbers (including target) will be positive integers.
  • The solution set must not contain duplicate combinations.

Example :

Input: candidates = [10,1,2,7,6,1,5] , target = 8

A solution set is:

[

  [1,7] ,

  [1,2,5] ,

  [2,6] ,

  [1,1,6]

] 

//用递归的思想解决问题
Set<List<Integer>> set = new HashSet<List<Integer>>();
public void recursion(int[] arr, int[] candidates, int restTarget){
    if(restTarget==0){
        List<Integer> list = new ArrayList<Integer>();
        for(int i=0;i<arr.length;++i)
            list.add(arr[i]);
        Collections.sort(list);
        set.add(list);
        return;
    }
    for(int i=0;i<candidates.length;++i){
        int temp = candidates[i];
        if(temp<=restTarget){
            int new_len;
            if(arr==null) new_len=1;
            else new_len = arr.length+1;
            int[] new_arr = new int[new_len];
            for(int j=0;j<new_len-1;++j){
                new_arr[j] = arr[j];
            }
            new_arr[new_len-1] = temp;
            int new_can_len = candidates.length-1;
            int[] new_candidates = new int[new_can_len];
            int k = 0;
            int j = 0;
            while(j<new_can_len){
                if(k==i)++k;
                new_candidates[j] = candidates[k];
                ++j;
                ++k;
            }
            recursion(new_arr,new_candidates,restTarget-temp);
        }
    }
}
View Code

  

类型二:链表重排序

25. Reverse Nodes in k-Group

Given a linked list, reverse the nodes of a linked list k at a time and return its modified list.

k is a positive integer and is less than or equal to the length of the linked list. If the number of nodes is not a multiple of k then left-out nodes in the end should remain as it is.

Example:

Given this linked list: 1->2->3->4->5

For k = 2, you should return: 2->1->4->3->5

For k = 3, you should return: 3->2->1->4->5 

// 解题思路
// 将链表分割成n个共k个元素的小段,对每一小段进行反向处理,最后一个小段若不满k个元素,则不进行反向处理

// 1)判断每一个小段是否有k个元素 ps:其实只有最后一小段可能不满k个元素,但由于无法判断是否到达最后一个小段,故对每个小段都判断是否有k个元素,若不满k个元素,即到达最后一个小段,也不反向处理
int count = 0;
ListNode temp = pre;
while(count<k&&temp!=null){
    ++count;
    temp = temp.next;
}
if(count!=k)break;

// 2)反向处理:对于每一个小段,将其分为两个部分,即待处理片段和已处理片段;待处理片段的第一个元素总是和已处理片段的最后一个元素相连,且待处理片段的第一个元素准备向已处理片段的第一个前面插入;由于该操作执行后,待处理片段的第一个元素要与其后的待处理元素断开联系,故在执行该操作前,必须先让已处理片段的最后一个元素与其之后的待处理元素相连。
//令pre指向已处理片段最后一个元素,curr指向已处理片段第一个元素,post指向待处理片段第一个元素
for(int i=1;i<k;++i){
    pre.next = post.next;
    post.next = curr;
    curr = post;
    post = pre.next;
}

// 3)在每个小段处理完毕后,指针需后移指向下一个分组,此时须主意使上一个分组的已处理片段的最后一个元素lastpre与后一个分组的已处理片段的第一个元素curr相连接。
if(lastpre==null)head = curr;
else lastpre.next = curr;
lastpre = pre;
pre = pre.next;
curr = pre;
post = pre.next;
View Code

  

31. Next Permutation (没做出来次数:1)

Implement next permutation, which rearranges numbers into the lexicographically next greater permutation of numbers.

If such arrangement is not possible, it must rearrange it as the lowest possible order (ie, sorted in ascending order).

The replacement must be in-place and use only constant extra memory.

Here are some examples. Inputs are in the left-hand column and its corresponding outputs are in the right-hand column.

1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1

//解题思路
//对于某个子串,其元素按降序排列,则对于这个子串中的元素来说,没有比该排列更大的数了,需延长该子串,使其元素不完全按降序排列
//即从右往左依次查询,直到找到第一个a[i]>a[i-1],此时a[i],a[i+1],...,a[n]按降序排列
//对于子串a[i-1],...,a[n],需调整其结构,使其仅比原来的排列大
//首先改变a[i-1]的值,使其比原来的a[i-1]大,但不能大很多,则应该在a[i],...,a[n]中找到比a[i-1]大的最小值a[k],并交换a[i-1]和a[k]
//为使调整后的a[i],...,a[n]子串最小,需对该子串按升序排列
int len = nums.length;
boolean flag = false;
for(int i = len-1;i>0;--i){
    if(nums[i]>nums[i-1]){
        flag = true;
        int local_max = nums[i];
        int local_index = i;
        for(int k = i+1;k<len;++k){
            if(nums[k]>nums[i-1]&&nums[k]<local_max){
                local_max = nums[k];
                local_index = k;
            }
        }
        int temp = nums[i-1];
        nums[i-1] = local_max;
        nums[local_index] = temp;
        for(int k = i;k<len-1;++k){
            for(int t = len-1;t>k;--t){
                if(nums[t]<nums[t-1]){
                    int temp2 = nums[t];
                    nums[t] = nums[t-1];
                    nums[t-1] = temp2;
                }
            }
        }
        break;
    }
}
if(!flag) {
    int i=0;
    int j = len-1;
    while(i<j){
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
        ++i;
        --j;
    }
}
View Code

 

类型三:栈的特性

32. Longest Valid Parentheses (没做出来次数:1)

Given a string containing just the characters '(' and ')', find the length of the longest valid (well-formed) parentheses substring.

Example :

Input: ")()())"

Output: 4

Explanation: The longest vaild parentheses substring is "()()".

//方法一:暴力搜索
//对于每一个可能的子串,判断该子串是否合法,从所有合法的子串中挑选最长子串
//但该方法需要巨大开销,时间复杂度达到O(n3)
public static boolean isValidParentheses(String s){
    Deque<String> stack = new LinkedList<String>();
    for(int i=0;i<s.length();++i){
        String temp = s.substring(i,i+1);
        if(temp.equals("("))stack.addFirst(temp);
        else if(stack.isEmpty())return false;
        else stack.removeFirst();
    }
    if(stack.isEmpty())return true;
    return false;
}
public int longestValidParentheses(String s) {
    int result = 0;
    int len = s.length();
    for(int i=0;i<len;++i)
        for(int j=i+1;j<=len;++j){
            String sub = s.substring(i,j);
            if(isValidParentheses(sub)){
                int sub_len = sub.length();
                result = sub_len > result ? sub_len:result;
            }
        }
    return result;
}

//方法二:栈有先进后出的特点,用栈存放元素的下标,在同一有效子串弹出时后弹出的元素有较小的值,表示当前子串更长。
public int longestValidParentheses(String s) {
    int maxans = 0;
    Deque<Integer> stack = new LinkedList<Integer>();
    stack.addFirst(-1);
    for (int i = 0; i < s.length(); i++) {
        if (s.charAt(i) == '(') {
            stack.addFirst(i);
        } else {
            stack.removeFirst();          
            if (stack.isEmpty()) {   //先将截断有效串的元素下标入栈,方便抵消上一个出栈操作(因为非“(”元素的下标入栈,使栈中永远有元素存在,不用考虑空栈情况)
            stack.addFirst(i);
            } else {
                maxans = Math.max(maxans, i - stack.getFirst()); //将引起出栈的当前“)”元素下标减去当前栈中最后入栈的元素下标,即得子串长度。
            }
        }
    }
    return maxans;
}       
View Code

 

42. Trapping Rain Water(没做出来次数:1)

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.

Example:

Input: [0,1,0,2,1,0,1,3,2,1,2,1]

Output: 6

// 解题思路:对于每一个bar,搜寻其左右两边是否有比它本身更高的bar,使这三个bar构成一个凹型槽用来盛水,当两端的bar比中间bar相差越大,盛水越多。

//方法一:暴力迭代,循环检测每一个bar,判断当前bar所处位置可以盛水的容量,并累加所有bar所处位置的盛水量。
public int trap(int[] height) {
    int ans = 0;
    int size = height.length;
    for (int i = 1; i < size - 1; i++) {
        int max_left = 0, max_right = 0;
        for (int j = i; j >= 0; j--) { //Search the left part for max bar size
            max_left = Math.max(max_left, height[j]);
        }
        for (int j = i; j < size; j++) { //Search the right part for max bar size
            max_right = Math.max(max_right, height[j]);
        }
        ans += Math.min(max_left, max_right) - height[i];
    }
    return ans;
}

//方法二:动态规划:由于在遍历每一个bar,寻找其两端最高bar的过程中是有重复操作的,比如,对于索引i,需要遍历从0到i的每一个bar寻找最高bar,而对于索引i+1,需要遍历从0到i+1的每一个bar寻找最高bar,其中0到i的步骤是重复子问题,故可设置一个memo表,记录已经计算过的子问题结果。
public int trap(int[] height) {
    if( height.length == 0 )return 0;
    int ans = 0;
    int size = height.length;
    int[] left_max = new int[size];
    int[] right_max = new int[size];
    left_max[0] = height[0];
    for (int i = 1; i < size ; i++) {
        left_max[i] = Math.max ( height[i], left_max[i-1] );            
    }
    right_max[size-1] = height[size-1];
    for (int i = size-2;i >= 0; i--) {
        right_max[i] = Math.max ( height[i], right_max[i+1] );
    }
    for (int i = 1; i < size-1; i++) {
        ans += Math.min ( left_max[i], right_max[i] ) - height[i];
    }
    return ans;
}

//方法三:利用栈的特性,若当前bar小于等于栈的顶部元素时入栈,而当前bar大于栈的顶部元素时,则当前bar和栈中顶部的头两个元素构成了一个凹型槽,根据构成这个凹型槽的三个bar可计算位于栈顶部的bar所处位置的盛水量。
public int trap(int[] height) {
    int ans = 0, current = 0;
    Deque<Integer> st = new LinkedList<Integer>();
    while (current < height.length) {
        while (!st.isEmpty() && height[current] > height[st.getFirst()]) {
            int top = st.getFirst();
            st.removeFirst();
            if (st.isEmpty())
                break;
            int distance = current - st.getFirst() - 1;
            int bounded_height = Math.min(height[current], height[st.getFirst()]) - height[top];
            ans += distance * bounded_height;
        }
        st.addFirst(current++);
    }
    return ans;
}
View Code

 

类型四:动态规划问题

所有的动态规划问题都可以从使用递归回溯的解决方案开始,但该方案是最耗时的选择;

若存在时间和空间复杂度的限制,考虑对递归回溯的解决方案进行优化处理:

1、使用memoization表对自上而下编程(还是递归)中的回溯进行优化处理;在回溯过程中,通过查看某局部结果的状态(该状态由之前的计算结果得出),可避免重新计算该结果。

2、采用自下而上的编程方式避免递归这个耗时的操作

55. Jump Game(没做出来次数:1)

Given an array of non-negative integers, you are initially positioned at the first index of the array.

Each element in the array represents your maximum jump length at that position.

Determine if you are able to reach the last index.

Example:

Input: [2,3,1,1,4]

Output: true

Input: [3,2,1,0,4]

Output: false

// 第一步:构造递归回溯解决方案 (很容易想到)
boolean flag = false;
public void Jump(int[] nums,int position){
    if(flag==true) return;
    if(position==nums.length-1){
        flag = true;
        return;
     }
     for(int i=nums[position];i>0;--i){
        int next_position = position+i;
        if(next_position<nums.length)Jump(nums,next_position);
    }
}
public boolean canJump(int[] nums) {
    Jump(nums,0);
    return flag;
}

// 第二步:添加memoization表
enum Memoization {UNKNOWN,GOOD,BAD}
Memoization[] memo; //构造memoization表,该表应用到整个递归过程,故作用域不仅局限于某个函数,故可设置为类的成员变量(类似于全局变量的作用)
boolean flag = false;
public boolean Jump(int[] nums,int position){                          
    if(memo[position]!=Memoization.UNKNOWN){                           //在每次递归开始,先检查memo状态,避免重复计算
        if(memo[position] == Memoization.GOOD){
            flag = true;
            return true;
        }else return false;
    }
    for(int i=nums[position];i>0;--i){
        int next_position = position+i;
        if(next_position<nums.length)
            if(Jump(nums,next_position)){                              //通过每次的回溯结果,对当前位置的memo进行更新
                memo[position] = Memoization.GOOD;
                return true;
            }
    }
    memo[position] = Memoization.BAD;                                  //同样是根据回溯结果更新memo表
    return false;
}
public boolean canJump(int[] nums) {
    memo = new Memoization[nums.length];
    for(int i=0;i<memo.length;++i)                                     //在调用递归函数前先对memo表初始化
        memo[i] = Memoization.UNKNOWN;
    memo[memo.length-1] = Memoization.GOOD;
        
    Jump(nums,0);                                                      //调用递归函数
    return flag;
}

// 第三步:自底向上编程,从destination向source倒推,以循环迭代取代递归函数
enum Memoization {UNKNOWN,GOOD,BAD}
public boolean canJump(int[] nums) {
    Memoization[] memo = new Memoization[nums.length];                 //迭代之前先将memoization表初始化
    for (int i = 0; i < memo.length; i++) {
        memo[i] = Memoization.UNKNOWN;
    }
    memo[memo.length - 1] = Memoization.GOOD;

    for (int position = nums.length - 2; position >= 0; position--) {  //从后向前倒推
        for (int j = 1; j <= nums[position]; j++) {
            int next_position = position+j;
            if (next_position<nums.length&&memo[next_position] == Memoization.GOOD) {
                memo[position] = Memoization.GOOD;
                break;
            }
        }
    }
    return memo[0] == Memoization.GOOD;
}
View Code

 

45. Jump Game II (没做出来次数:1,和上题同解题思路,重做一遍立马解决)

Given an array of non-negative integers, you are initially positioned at the first index of the array.

Each element in the array represents your maximum jump length at that position.

Your goal is to reach the last index in the minimum number of jumps.

Example:

Input: [2,3,1,1,4]

Output: 2

 

84. 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.

Example:

Input: [2,1,5,6,2,3]

Output: 10

// 耗时做法(超时):对任意两个给定bar,每次都寻找其构成区间内的最低bar值,并计算该给定区间的有效面积
int getArea(int[] heights,int i,int j){
    int min_height = heights[i];
    for(int k=i+1;k<=j;++k){
        if(heights[k]<min_height){
            min_height = heights[k];
        }
    }
    return min_height*(j-i+1);
}
public int largestRectangleArea(int[] heights) {
    int max_area = 0;
    for(int i=0;i<heights.length;++i)
        for(int j=i;j<heights.length;++j){
            int temp = getArea(heights,i,j);
            if(temp>max_area)max_area = temp;
        }
    return max_area;
}

//因为多个给定区间的最低bar值由同一个bar决定,即他们存在重叠子问题。故可创建一个min_height表,记录需重复计算的值,避免重复计算,提升效率。
public int largestRectangleArea(int[] heights) {
    int maxArea = 0;
    int len = heights.length;
    int[] min_height = new int[len];
    for(int i=0;i<len;++i){
        min_height[i] = heights[i];
        for(int j = i+1;j<len;++j){
            min_height[j] = min_height[j-1]<=heights[j]?min_height[j-1]:heights[j];
        }
        for(int j = i;j<len;++j){
            if(min_height[j]*(j-i+1)>maxArea)maxArea = min_height[j]*(j-i+1);
        }
    }  
    return maxArea;
}
View Code

 

85. Maximal Rectangle(没做出来次数:1)

Given a 2D binary matrix filled with 0's and 1's, find the largest rectangle containing only 1's and return its area.

Example:

Input: 

  [ ["1","0","1","0","0"],

  ["1","0","1","1","1"],

  ["1","1","1","1","1"],

  ["1","0","0","1","0"] ]

Output: 6

//对于每一行,将该行即其上面的所有行合并,构成类似上题的柱状图,并以处理上题的方法同样来处理该题。
public int maximalRectangle(char[][] matrix) {
    int maxArea = 0;
    int m = matrix.length;
    if(m==0)return 0;
    int n = matrix[0].length;
    if(n==0)return 0;
    int[] min_height = new int[n];
    int[] heights = new int[n]; 
    for(int k=0;k<m;++k){
        for(int i=0;i<n;++i){
            if(matrix[k][i]-'0'==0)heights[i]=0;
            else ++heights[i];
        }
        for(int i=0;i<n;++i){
            min_height[i] = heights[i];
            for(int j = i+1;j<n;++j){
                min_height[j] = min_height[j-1]<=heights[j]?min_height[j-1]:heights[j];
            }
            for(int j = i;j<n;++j){
                if(min_height[j]*(j-i+1)>maxArea)maxArea = min_height[j]*(j-i+1);
            }
        }
    }
    return maxArea;
}
View Code

 

分治方法、贪心算法和动态规划

首先,这三种方法都用到了递归的求解步骤:

分治方法:分解原问题,让算法一次或多次递归地调用自身以解决规模较小的子问题,再合并子问题的解来建立原问题的解;

特征:每个被分解的子问题相互独立,不应有大量子问题重叠,否则应该放弃使用分治方法。

贪心算法:在每一步做出局部最优的选择,并希望这样的选择能导致全局最优解。贪心算法并不保证得到全局最优解,只是对很多实际问题确实得到了最优解;解题思路:首先考虑使用动态规划解决问题,然后证明一直做出贪心选择可得到最优解;即先确定问题具有最优子结构性质,然后证明若做出贪心选择后,只会剩下一个子问题,而其它子问题的解为空,这样,本次的贪心选择与剩余子问题最优解合并后可构成原问题的最优解;

特征:既有最优子结构性质,又有贪心选择性质

动态规划方法:动态规划应用于子问题重叠的情况,具有公共子子问题,动态规划对每一个子子问题只求解一次,将结果保存在表格中,无需重新计算;

最优子结构性质证明:假设某个选择构成里最优解,分析这个选择产生的子问题,证明子问题的解也是最优;

解题思路:明确递归式的结束条件和初始值,将原问题划分为两个子问题,并将子问题作为新的问题进行递归迭代,通过组合两个相关子问题的最优解得到原问题的可能解;遍历每一个划分点并在所有可能的两段划分方案中选取组合收益最大的方案,构成原问题的最优解;

特征:具有最优子结构性质重复子问题性质。

 

类型五:树

105. Construct Binary Tree from Preorder and Inorder Traversal (没做出来次数:1)

Given preorder and inorder traversal of a tree, construct the binary tree.

Note:
You may assume that duplicates do not exist in the tree.

For example, given

preorder : [3,9,20,15,7]

inorder : [9,3,15,20,7]

Return the following binary tree:

          3

         / \

       9   20

            / \

          15   7

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */    
public TreeNode buildTree(int[] preorder, int[] inorder) {
    return createTree(0,inorder.length-1,preorder,inorder,0);
}
//start和end划定inorder数组的区间
public TreeNode createTree(int start,int end, int[] preorder, int[] inorder, int offset){
    if(start>end)return null;
    //根节点的值即为前序遍历数组的第一个元素
    int root_val = preorder[start+offset];
    //为二叉树链表建立根节点
    TreeNode root = new TreeNode(root_val);

    //找到中序遍历序列数组中根节点的下标
    int i = get_root_idx(preorder,inorder,start+offset);    

    //构造根节点的左子树
    root.left = createTree(start,i-1,preorder,inorder,offset+1);
    //构造根节点的右子树
    root.right = createTree(i+1,end,preorder,inorder,offset);
    //返回根节点
    return root;
}
//获取根节点在中序遍历数组中的索引下标,没找到即返回-1
int get_root_idx(int[] preorder, int[] inorder,int pre_start){
        int i;
        int ch;
    //该循环的初始化操作包括了把ch赋值为preorder[]数组的首个元素
    //因为preorder[]的首个元素是根节点
        for(i=0, ch=preorder[pre_start]; i<inorder.length; i++)
            if(inorder[i]==ch)
                return i;
        return -1;
}
    
View Code

 

类型六:利用现有数据结构构造新的数据结构(数据结构的扩张)

380. Insert Delete GetRandom O(1)

Design a data structure that supports all following operations in averageO(1) time.

1)insert(val): Inserts an item val to the set if not already present.

2)remove(val): Removes an item val from the set if present.

3)getRandom: Returns a random element from current set of elements. Each element must have the same probability of being returned.

//随机访问特性:一个是数组作为底层的线性表结构,代表集合是arraylist,一个是数组作为底层的散列表结构,而用到了散列表的集合有hashmap,故优先考虑这两个集合作为新数据结构的组合元素;
//hashset不具有随机访问特性,它有add,remove, 却没有get方法,只能通过iterator遍历取数
//以常数时间插入和删除元素,首先考虑链表结构、hashset和hashmap,但链表不具有随机访问特性;通过观察发现既满足随机访问特性,又满足单位时间添加和删除的集合就是hashmap,由此可知利用hashmap是问题核心。
//方法是:将元素插入list,同时将元素作为键,元素在list中的索引作为值构成的键值对插入map,它们都有随机访问特性;但如何使arraylist增删元素的时间变为常数值?方法是将待删除元素与arraylist最后一个元素交换,然后删除最后一个元素即可。
//arraylist和hashmap底层数组是一个动态表结构,它们都有一个负载因子,在表中插入和删除元素的过程中只有低于负载因子或者空间不足时,才会重新分配内存,用摊还算法分析其插入和删除操作的代价仅为常数。

class RandomizedSet {
    List<Integer> list;
    Map<Integer,Integer> map;
    /** Initialize your data structure here. */
    public RandomizedSet() {
        list = new ArrayList<Integer>();
        map = new HashMap<Integer,Integer>();
    }
    
    /** Inserts a value to the set. Returns true if the set did not already contain the specified element. */
    public boolean insert(int val) {
        if(map.get(val)!=null)return false;
        list.add(val);
        map.put(val,list.size()-1);
        return true;
    }
    
    /** Removes a value from the set. Returns true if the set contained the specified element. */
    public boolean remove(int val) {
        Integer indice = map.get(val);
        if(indice==null)return false;
        if(indice.intValue()!= list.size()-1){
            int temp = list.get(list.size()-1);
            list.set(list.size()-1,val);
            list.set(indice,temp);
            map.replace(temp,indice);
        }
        map.remove(val);
        list.remove(list.size()-1);
        return true;
    }
    
    /** Get a random element from the set. */
    public int getRandom() {
        return list.get((int)(Math.random()*list.size()));
    }
}
View Code

 

381. Insert Delete GetRandom O(1) - Duplicates allowed

Design a data structure that supports all following operations in averageO(1) time.

Note: Duplicate elements are allowed.

1)insert(val): Inserts an item val to the collection.

2)remove(val): Removes an item val from the collection if present.

3)getRandom: Returns a random element from current collection of elements. The probability of each element being returned is linearly related to the number of same value the collection contains.

//该题与上题的不同之处在于存在相同元素,因为arraylist中可以出现相同元素,而hashmap中不允许出现相同的键,而我们是把arraylist中的元素作为hashmap中的键,那么必有许多索引无法作为map的值;
//可将这些相同值的索引用一个list存储,这个list中的元素指向相同的值,那么这个值可作为提取该list的键,list就是对应的索引集合。

class RandomizedCollection {
    List<Integer> list;
    Map<Integer,List<Integer>> map;
    /** Initialize your data structure here. */
    public RandomizedCollection() {
        list = new ArrayList<Integer>();
        map = new HashMap<Integer,List<Integer>>();
    }
    
    /** Inserts a value to the collection. Returns true if the collection did not already contain the specified element. */
    public boolean insert(int val) {
        List<Integer> link  = map.get(val);
        if(link==null){
            link = new LinkedList<Integer>();
            list.add(val);
            link.add(list.size()-1);
            map.put(val,link);
            return true;
        }else{
            list.add(val);
            link.add(list.size()-1);
            return false;
        }
    }
    
    /** Removes a value from the collection. Returns true if the collection contained the specified element. */
    public boolean remove(int val) {
        List<Integer> link = map.get(val);
        if(link==null)return false;
        Integer indice = link.get(0);
        int temp = list.get(list.size()-1);
        list.set(list.size()-1,val);
        list.set(indice,temp);
        List<Integer> link_replace  = map.get(temp);
        link_replace.remove(link_replace.size()-1);
        Iterator<Integer> it = link_replace.iterator();
        if(!it.hasNext()||it.next()>=indice)link_replace.add(0,indice);
        else {
            int pre=0;
            while(it.hasNext()) {
                pre +=1;
                if(it.next()>indice) {
                    link_replace.add(pre, indice);
                    break;
                }
            }
            link_replace.add(pre, indice);
        }
        
        list.remove(list.size()-1);
        link.remove(0);
        if(link.size()==0)map.remove(val);
        return true;
    }
    
    /** Get a random element from the collection. */
    public int getRandom() {
        return list.get((int)(Math.random()*list.size()));
    }
}
View Code

 

类型七:益智题

442. Find All Duplicates in an Array

Given an array of integers, 1 ≤ a[i] ≤ n (n = size of array), some elements appear twice and others appear once.

Find all the elements that appear twice in this array.

Could you do it without extra space and in O(n) runtime?

//将数组元素当作新的索引,对新索引对应的元素取反,而数组中相同元素意味着该元素作为索引的新的数组元素会被访问不止一次,在其第二次被访问时,该值为负数,表示该索引重复出现,该索引即为重复的元素。
public List<Integer> findDuplicates(int[] nums) {
    List<Integer> result = new ArrayList<Integer>();
    for(int i=0;i<nums.length;++i){
        if(nums[Math.abs(nums[i])-1]>0){
            nums[Math.abs(nums[i])-1] *=-1;
        }else {
            result.add(Math.abs(nums[i]));
        }
    }
    return result;
}
View Code

 

565. Array Nesting

A zero-indexed array A of length N contains all integers from 0 to N-1. Find and return the longest length of set S, where S[i] = {A[i], A[A[i]], A[A[A[i]]], ... } subjected to the rule below.

Suppose the first element in S starts with the selection of element A[i] of index = i, the next element in S should be A[A[i]], and then A[A[A[i]]]… By that analogy, we stop adding right before a duplicate element occurs in S.

Example :

Input: A = [5,4,0,3,1,6,2]

Output: 4

Explanation:  One of the longest S[K]: S[0] = { A[0], A[5], A[6], A[2] } = { 5, 6, 2, 0 }

//暴力搜索: 有重复劳动(Time Limit Exceeded)
public int arrayNesting(int[] nums) {
    Set<Integer> set = new HashSet<Integer>();
    int maxlen = 0;
    for(int i=0;i<nums.length;++i){
        int sum = 0;
        int j=i;
        while(!set.contains(nums[j])){
            ++sum;
            set.add(nums[j]);
            j = nums[j];
        }
        if(maxlen<sum)maxlen = sum;
        set.clear();
    }
    return maxlen;
}

//❌错误做法:因为有重复劳动,企图加个memo表记录从这一步开始到循环结束的长度,避免重复计算;该做法计算的循环链长比实际高,因为有重复估计已经到过的链接点
public int arrayNesting(int[] nums) {
    Set<Integer> set = new HashSet<Integer>();
    int[] memo = new int[nums.length];
    for(int i=0;i<nums.length;++i)      //创建一个memo表
        memo[i] = -1;
    for(int i=0;i<nums.length;++i){
        int sum = 0;
        int j=i;
        while(!set.contains(nums[j])){
            if(memo[j]!=-1){                 //当行走到j点时,检查是否有以j点为起始点的循环链长记录,避免重复计算
                sum += memo[j];            //总的链长等于从i到j的链长加上以j为起始的链长;
                break;                             //该做法有问题,因为从i到j的链和以j为起始的完整链可能有重合部分,导致链节点重复计算
            }
            ++sum;
            set.add(nums[j]);
            j = nums[j];
        }
        memo[i] = sum;                      //将从i点出发的循环链长做记录
        set.clear();
    }
    int maxlen = 0;
    for(int i=0;i<memo.length;++i){
        if(memo[i]>maxlen) maxlen = memo[i];
    }
    return maxlen;
}

//改进做法:由于数组中n个数分别从0到n-1无重复,故数组中每个元素作为索引新到达的数组位置各不相同,故各循环链表无交叉,一个循环链表各节点被访问不会影响另一个链表的计算。每个循环链表可已任意一个内节点作为起始节点计算,且只需计算一次,由visited表控制该节点是否已被访问
public int arrayNesting(int[] nums) {
    Set<Integer> set = new HashSet<Integer>();
    int maxlen = 0;
    boolean[] visited = new boolean[nums.length];
    for(int i=0;i<visited.length;++i)
        visited[i] = false;
    for(int i=0;i<nums.length;++i){
        if(visited[i]==false){
            int sum = 0;
            int j=i;
            while(!set.contains(nums[j])){
                ++sum;
                set.add(nums[j]);
                j = nums[j];
                visited[j] = true;
            }
            if(maxlen<sum)maxlen = sum;
            set.clear();
        }
    }
    return maxlen;
}
View Code

 

581. Shortest Unsorted Continuous Subarray

Given an integer array, you need to find one continuous subarray that if you only sort this subarray in ascending order, then the whole array will be sorted in ascending order, too. 

You need to find the shortest such subarray and output its length.

Example:

Input: [2,6,4,8,10,9,15]  Output: 5             Input: [1,2,3,4]  Output: 0                 Input: [1,2,3,3,3]  Output: 0                Input: [1,3,2,2,2]  Output: 4

Input: [1,3,2,3,3]  Output: 2                     Input: [2,3,3,2,4]  Output: 3              Input: [1,2,4,5,3]  Output: 3                Input: [1,3,5,2,4]  Output: 4

//暴力检索:对每一对nums[i]和nums[j]数组元素,判断是否存在倒序,若存在,记录最大边界
public int findUnsortedSubarray(int[] nums) {
    int l = nums.length, r = 0;
    for (int i = 0; i < nums.length - 1; i++) {
        for (int j = i + 1; j < nums.length; j++) {
            if (nums[j] < nums[i]) {
                r = Math.max(r, j);
                l = Math.min(l, i);
            }
        }
    }
    return r - l < 0 ? 0 : r - l + 1;
}

//方法二:牺牲空间,节约时间,对数组克隆后排序,将排序数组和原数组中元素逐一比较,找到不同元素对应下标。
View Code

 

转载于:https://www.cnblogs.com/dmzxxmeng/p/10865037.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值