天天看点

top interview questions 4

总结下,从奇虎的两次测试来看,数组的变相二分查找,以及利用map与数组,结合dp,可以有效的解决很多问题。

扫描线这块要放上pair。

The skyline problem :是将起始位置的height置位负的,将结束位置的高不变。

放上一个int数组,构成一组pair

Arrays.fill(arr, 0);将整个数组元素填充为0

// 求最大公约数

lic int findGcd(int a, int b){
        return (a ==  || b == ) ? a + b : findGcd(b, a % b);
    }
           

之前听课的时候老师也说过分治法就像一个女王大人,处于root的位置,然后派了两位青蛙大臣去处理一些事物,女王大人只需要管好自己的val是多少,然后把两个大臣的反馈直接加起来就可以了。个人认为分治法算是比较接近普通人思维的一种方法了。

实现一个带括号的计算器

思路:1.处理括号,徐兆第一个反括号,计算

2.计算, 得到+-*/的四个不同的index,不存在返回str.length();

3.对于加减,进行stack1,stack2缓存,对于乘除,直接计算。

2给定字符串,输出包含偶数个字符的子串的个数

public static void main(String[] args) {
            Scanner sc = new Scanner(System.in);
            String str = sc.nextLine();
            int cur = ;
            Map map = new HashMap();
            map.put(, );//回到初始状态也要先加上1
            int resu = ;
            char[] ch = str.toCharArray();
            for (int i = ; i < ch.length; i++) {
                int temp = ch[i] - 'a';
                cur = cur ^ ( << temp);//此处注意是亦或
                if (map.containsKey(cur)) {
                    resu += (int)map.get(cur);
                    map.put(cur, (int)map.get(cur) + );
                } else {
                    map.put(cur, );
                }
            }
            System.out.println(resu);
        }
           

3列表任务与临时任务

输入 列表任务个数 N 临时任务个数 M

输入N个数,表示列表任务执行的时刻

输入M个数,表示临时任务执行的熟客

输出M个数,表示临时任务执行时间

思路:1.这个主要是利用第一个输入数组作为区间标定,M数组总的时刻t,在N数组中找出大于t的有效时间间隔。可以用TreeSet来缓存有效时间段开始时刻(不包含端点),对于最大的值,TreeSet应该并入max+ 1到正无穷,用用Map来缓存有效时间结束的时刻,每次来了数据,先找lower的值,在找到结束的值,如果包含,就输出val的值,否则输出higher的值。

2.吧列表任务用数组进行缓存,lower_bound函数返回大于等于该元素的下表,如果返回值不等于,那么直接输出,如果等于,那么进行二分查找,找到下一个大于等于的值,并且后面不连续的值。很巧妙的方法。

int solve(int left, int right) {
    int idx;
    int start = left;
    while (left <= right) {
        int mid = (left + right) >> ;
        if (a[mid] - a[start] == mid - start) {
            left = mid + ;
            idx = mid;
        }
        else {
            right = mid - ;
        }
    }
    return a[idx] + ;
}
           

幼儿园小朋友排队,转化为数组逆序对的问题

public static int reseversepairs(int nums[]) {
            int[] res = new int[nums.length];
            return mergeSort(nums, res, , nums.length - );
        }
        public static  int mergeSort(int[] nums,int[] res, int left, int right) {
            if (left >= right) {
                return ;
            }   
            int mid = left + ((right - left) >> );
            int numl = mergeSort(nums, res, left, mid);
            int numr = mergeSort(nums, res, mid + , right);
            int merm = merge(nums, res, left , mid, right);
            return numl + numr + merm;
        }
        public static int merge(int[] nums, int[] res, int left, int mid, int right) {
           int le = left, ri = mid + , idx = left;
           int cnt = ;
           while (le <= mid && ri <= right) {
               if (nums[le] > nums[ri]) {
                   res[idx++] = nums[ri++];
                   cnt += mid - le + ;
               } else {
                   res[idx++] = nums[le++];
               }
           }
           while (le <= mid) {
               res[idx++] = nums[le++];
           }
           while (ri <= right) {
               res[idx++] = nums[ri++];
           }
           for (int k = left; k <= right; k++) {
               nums[k] = res[k];
           }
           return cnt;
        }
           

二分问题的一个小结

第一境界:会写程序

Find First Position of Target

Find Last Position of Target

第二境界:找到第一个/最后一个满足某个条件的位置/值

Search a 2D Matrix

Find Minimum in Rotated Sorted Array

第三境界:保留有解的那一半

Find Peak Element

找到第一个位置

public static int findFirstPo(int[] nums, int val) {
        int left = ;
        int right = nums.length - ;
        int mid = ;
        while (left < right) {
            mid = (left + right) >> ;
            if (nums[mid] > val) {
                right = mid - ;
            } else if (nums[mid] < val) {
                left = mid + ;
            } else {
                right = mid;
            }

        }
        return nums[left] == val ? left : -;//此处的mid具有一定的滞后性,故采用left
    }
           

区间缩小-> 剩下两个下标->判断两个下标

注:不要把缩小区间和得到答案放在一个循环里面,容易出问题,增加难度

找到最后一个位置

因为除法存在下取整数的问题,所以第一个下标没问题,第二个下标会出现问题

public static int findFirstPo(int[] nums, int val) {
        int left = ;
        int right = nums.length - ;
        int mid = ;
        while (left +  < right) {// 此处更改
            mid = (left + right) >> ;
            if (nums[mid] > val) {
                right = mid - ;
            } else if (nums[mid] < val) {
                left = mid + ;
            } else {
                left = mid;// 此处更改
            }
        }
        return nums[right] == val ? right : nums[left] == val ? left ; -;//此处的mid具有一定的滞后性,故采用left
    }
           

74. Search a 2D Matrix

public boolean searchMatrix(int[][] matrix, int target) {
        if (matrix == null || matrix.length <  || matrix[].length < ) {
            return false;
        }
        int left = , right = matrix.length - ;
        int mid = ;
        while (left +  < right) {// find the first less than target
            mid = (left + right) >> ;
            if (matrix[mid][] > target) {
                right = mid;
            } else {
                left = mid;
            }
        }
        int index = matrix[right][] <= target ? right : left;
        int left2 = , right2 = matrix[].length - ;
        int mid2 = ;
        while (left2 +  < right2) {
            mid2 = ((right2 - left2) >> ) + left2;
            if (matrix[index][mid2] > target) {
                right2 = mid2 - ;
            } else if (matrix[index][mid2] < target) {
                left2 = mid2 + ;
            } else {
                return true;
            }
        }
        if (matrix[index][left2] == target) return true;
        if (matrix[index][right2] == target) return true;
        return false;
    }
           

240. Search a 2D Matrix II

令x = 0, y = length;

然后进行遍历迭代,很巧妙的方法

153. Find Minimum in Rotated Sorted Array

找到旋转数组的最小值,这个也是个很巧妙, 以后考虑每个二分采用left+ 1 < right ;

取target为数组的最后一个值,寻找到第一个小于等于这个值的数。

154. Find Minimum in Rotated Sorted Array II

旋转数组中存在重复的元素如何改变

public class Solution {
    public int findMin(int[] nums) {
        int left = ;
        int right = nums.length -;
        int target = nums[right];
        int mid = ;
        while (left +  < right) {
            mid = left + ((right - left) >> );
            if (nums[mid] < nums[right]) {
                right = mid;
            } else if (nums[mid] > nums[right]){
                left = mid;
            } else {
                right--;// 相等就往下减
            }
        }
        return Math.min(nums[left], nums[right]);
    }
}
           

35. Search Insert Position

二分法取等号的时候的一个例子,在检索要插入的位置,要返回0或者nums.length。

173. Binary Search Tree Iterator

二叉树迭代器,每次都返回当前树种最小的数值,要求hasnext()以及next()都是O(1)时间复杂度,空间复杂度为O(k),k为树的深度。

分治法的一个结

一、概念

对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解决这些子问题,然后将各子问题的解合并得到原问题的解。这种算法设计策略叫做分治法。

二、分治法适用情况

1)问题的规模缩小到一定程度就可以容易解决

2)具有最子结构的性质(递归思想)

3)子问题的解可以合并为原问题的解(关键,否则为贪心法或者动态规划法)

4)子问题是相互独立的 ,子问题之间不包含公共的子子问题(重复解公共的子问题,一般用动态规划法比较好)

三、分治法的步骤

step1 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题

step2 解决:子问题规模较小而容易被解决则直接解决,否则递归地解各个子问题

step3 合并:将各个子问题的解合并为原问题的解

设计模式

Divide-and-Conquer(P)

if |P|<=N0 then return (ADHOC(P))

将P分解为较小的字问题P1,P2,…,Pk

for i<-1 to kß

do Yi <- Divide-and-Conquer(Pi) 递归解决Pi

T <- MERGE(Y1,Y2,…,Yk) 合并子问题

return (T)

链表这块

outline:

Dummy Node in Linked List

Remove Duplicates from Sorted List II

Reverse Linked List II

Partition List

Basic Linked List Skills

Sort List

Reorder List

Two Pointers in Linked List (Fast-slow pointers)

Merge K Sorted Lists

82. Remove Duplicates from Sorted List II

删除重复的元素,这个是如果本身存在重复,一样也得删除

dummy要放一个傀儡,作为链表的头

// 需要朗读并背诵的段落
    public ListNode deleteDuplicates(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode dummpy = new ListNode();
        dummpy.next = head;
        head = dummpy;
        int val = ;
        while(head.next != null && head.next.next != null) {
            if (head.next.val == head.next.next.val) {
                val = head.next.val;
                while (head.next != null && head.next.val == val) {
                    head.next = head.next.next;
                }
            } else {
                head = head.next;
            }
        }
        return dummpy.next;
    }
           

92. Reverse Linked List II

实现链表部分反转,dummy

public ListNode reverseBetween(ListNode head, int m, int n) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode dumpy = new ListNode();
        dumpy.next = head;
        head = dumpy;
        for (int i = ; i < m; i++) {
            head = head.next;
        }
        ListNode pre = head;// pre 以及 mnode这两个节点都是要进行
        ListNode mnode = head.next;
        ListNode nnode = mnode;
        ListNode post = mnode.next;
        for (int j = m; j < n; j++) {
            ListNode temp = post.next;
            post.next = nnode;
            nnode = post;
            post = temp;
        }
        mnode.next = post;
        pre.next = nnode;
        return dumpy.next;
    }
           

86 Partition List

dummy的典型使用,归并排序的一个基础, 给定一个值,小于这个值的排在大于这个值的前面

总结

因为链表算是比较基础的内容了,所以也不需要太多的东西。大家就把几个重要的trick掌握好就可以了,还是需要多多练习。

知识点如下:

1.dummy的使用

2.fast-slow pointer的使用

大家都惧怕的“动规”

三种类型的动态规划,坐标动态规划,单序列动态规划,多序列动态规划

动态规划入门级文章 : 背包九讲 赞!

http://blog.csdn.net/ling_du/article/details/41594767

Outline:

了解动态规划

Triangle

动态规划的适用范围

坐标型动态规划

Minimum Path Sum

Climbing Stairs

Jump Game

Longest Increasing Subsequence

单序列动态规划

Word Break

双序列动态规划

Longest Common Subsequence

总结

这里也就引出了动态规划和分治法的根本区别:动态规划存在重复计算的部分,而分治法是没有的,也就是说,由全局的问题分成子问题的时候,分治法的子问题是完全独立的,相互之间没有交集,而动态规划的方法是有交叉部分的。

2.动态规划的适用范围

这个内容我个人认为对于面试是非常重要的,因为之前有面试官给我出过一个求出所有可行解的问题,当时我就是用dp来考虑,显然最后就用一个三维动态规划来解决了,这种就给了自己很大的麻烦。所以动态规划在一定程度上很容易和DFS这样的场景混淆。

满足下面三个条件之一:

- 求最大值最小值

- 判断是否可行

- 统计方案个数

则极有可能是使用动态规划的方法来求解的。之前求所有解的话,肯定是要去遍历然后求出满足情况的解的方法,而不是动态规划这样的模式。

以下情况是不使用动态规划的情况:

- 求出所有具体的方案

- 输入数据是一个集合而不是序列

- 暴力算法的复杂度已经是多项式级别

动态规划擅长于优化指数级别的复杂度到多项式级别

动态规划就是四个重要的要素:

- 状态

- 方程

- 初始化

- 答案

64. Minimum Path Sum

采用的是二维动态规划

120. Triangle

同上

45. Jump Game II

这个是给定一个数组,用最小的步数jump到数组尾部

可以采用动态规划,trick,对于每一个i,先初始化其为最大值,然后从0开始进行dp的累加,一旦可达,更新dp[i],采用这个方法,会出现TLE

最优的思路是greedy,贪心

300. Longest Increasing Subsequence

两种思路:思路1:dp缓存的是当前位置的最长递增组序列的长度,初始化dp每个值都为1

思路2:dp缓存的是当前下标+1长度的最长递增子序列的最小值,返回size的值

139. Word Break

看这个字符串能否在拆分的过程中每一个子串都在词典中

这个是单序列动态规划问题,除了坐标动态规划以外,一般都需要多定义一位,dp[i]缓存的是从0到i结点,能否被词典中单词完整分割

双指针

80. Remove Duplicates from Sorted Array II

有序数组要求最多可以重复2个元素,可以删除多于的数组

这个问题可以扩展为k个

public int removeDuplicates(int[] nums) {
        int i = ;
        for (int n : nums) {
            if (i <  || n > nums[i - ]) {
                nums[i++] = n;
            }
        }
        return i;
    }
           

407. Trapping Rain Water II

存储雨水,这个自己定义一个单元,存储row,col以及height,利用优先队列进行存储,先将四周的数据引入到队列中,然后向周围进行扩散。定义出visited进行数据访问节点的标记

209. Minimum Size Subarray Sum

满足累加和大于某个数的最小的子数组,这个用滑动窗口来做,注意while的使用

还有一个用二分的方法,比较trick

30. Substring with Concatenation of All Words

给定一个字符串,给定一个字符串列表,求出字符串中连续包含字符串列表的子串的起始位置。

这个是滑动窗口的变,采用两个map来实现。

无AC原因, left += j + m;写错了

public class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        // 采用map作为滑动窗口,
        List<Integer> list = new LinkedList<Integer>();
        if (s.length() <  || words.length < ) {
            return list;
        }
        Map<String, Integer> slide = new HashMap<String, Integer>();
        for (int i = ; i < words.length; i++) {
            slide.put(words[i], slide.getOrDefault(words[i], ) + );
        }
        int n = s.length();
        int m = words[].length();
        int num = words.length;
        for (int i = ; i < m; i++) {
            int count = , left = i;
            Map<String, Integer> map = new HashMap<String, Integer>();
            for (int j = i; j <= n - m; j+= m) {
                String str = s.substring(j, j + m);
                if (slide.containsKey(str)) {
                    map.put(str, map.getOrDefault(str, ) + );
                    if (map.get(str) <= slide.get(str)) {
                        count++;
                    } else {
                        while (map.get(str) > slide.get(str)) {
                            String str1 = s.substring(left, left + m);
                            map.put(str1, map.get(str1) - );
                            if (map.get(str1) < slide.get(str1)) 
                                count--;
                            left += m;
                        }
                    }
                    if (count == num) {
                        list.add(left);
                        map.put(s.substring(left, left + m), map.get(s.substring(left, left + m)) - );
                        count--;
                        left += m;
                    }
                } else {
                    map.clear();
                    left = j + m;
                    count = ;
                }
            }
        }
        return list; 
    }
}
           

421. Maximum XOR of Two Numbers in an Array

数组中两个数异或所能得到的最大的数

思路1:采用的是位操作的方法加上set存储。依次采用mask取得前数组中前i位的prefix,用dummymax表示当前max的第i位变成1,如果dummymax^ 某个数在set中包含,则更新max的值。

思路2:利用单词查找树,先将数字依次插入到单词查找树中,遍历数组,每次取到存在与当前位置相反的进行下一次更新,同时xor更新xor

128. Longest Consecutive Sequence

给定数组,从中选取子序列,得到最长的增长子序列。

这个可以采用一个并查集的结构进行求解

// 两个数组一个做父节点的映射,一个进行size的统计,利用一个map进行下标的映射关系。
public class Solution {
    int[] parent;
    int[] size;
    public int longestConsecutive(int[] nums) {
        if (nums.length < ) {
            return nums.length;
        }
        parent = new int[nums.length];
        size = new int[nums.length];
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        for (int i = ; i < nums.length; i++) {
            if (map.containsKey(nums[i])) {
                continue;
            }
            map.put(nums[i], i);
            parent[i] = i;
            size[i] = ;
            if (map.containsKey(nums[i] - )) {
                union(map.get(nums[i] - ), i);
            }
            if (map.containsKey(nums[i] + )) {
                union(i, map.get(nums[i] + ));
            }
        }
        int max = ;
        for (int i = ; i < nums.length; i++) {
            max = Math.max(max, size[i]);
        }

        return max;
    }
    public int find (int x) {
        if (parent[x] == x) {
            return x;
        }
        parent[x] = find(parent[x]);
        return parent[x];
    }
    public void union (int a, int b) {
        int pa = find(a);
        int pb = find(b);
        if (pa != pb) {
            if (size[pa] > size[pb]) {
                parent[pb] = pa;
                size[pa] += size[pb];
            } else {
                parent[pa] = pb;
                size[pb] += size[pa];
            }
        }   
    }
}
           

赛码网京东考试通过概率的dp问题

1.小明n个考试,通过60%以上算考试通过,n个考试的通过概率依次为输入

定义dp[i][j]为i个题目中做对j各个的概率,最后再的加

2.石头分堆问题(这个是数学问题)

图的初始化一个比较好的方式是利用Map

几个sum的问题

1. Two Sum

要求:单个数组,输出两个数的和等于某个特定值得下标

思路:用map进行合理的缓存

167. Two Sum II - Input array is sorted

要求:单个数组,同上,但是已经是有序的,需要

思路:利用二分的方法

15. 3Sum

要求:单个数组,输出3个下标,使得对应的和为0

思路:先排序,时间复杂度为O(n平方),从下标为0开始,用while循环进行嵌套

16. 3Sum Closest

要求:单个数组,数组有序第一时间考虑二分

思路:for’循环加上while’循环

18. 4Sum

要求:单个数组,返回4个下标,得到合理的解

思路:时间复杂度为O(n三次方)

454. 4Sum II

要求:4个数组,返回每个数组合理的下标

思路:一个map,缓存两个数组,另外两个,因为输出的是个数,所以可以