秋招接近尾声,我总结了 牛客、WanAndroid 上,有关笔试面经的帖子中出现的算法题,结合往年考题写了这一系列文章,全部文章均与 LeetCode 进行核对、测试。欢迎食用java
本文将覆盖 「字符串处理」 + 「动态规划」 方面的面试算法题,文中我将给出:node
仓库地址:超级干货!精心概括视频、归类、总结
,各位路过的老铁支持一下!给个 Star !
android
如今就让咱们开始吧!git
二叉搜索树(Binary Search Tree
),它或者是一棵空树,或者是具备下列性质的二叉树:github
给定一个二叉树,判断其是不是一个有效的二叉搜索树。面试
假设一个二叉搜索树具备以下特征:算法
左子树
只包含小于
当前节点的数。右子树
只包含大于
当前节点的数。示例 :数据库
输入: 5 / \ 1 4 / \ 3 6 输出: false 解释: 输入为: [5,1,4,null,null,3,6]。 根节点的值为 5 ,可是其右子节点值为 4 。 复制代码
乍一看,这是一道很简单的题。只须要遍历整棵树,检查 node.right.val
> node.val
和 node.left.val
< node.val
对每一个结点是否成立。编程
问题是,这种方法并不老是正确。不只右子结点要大于
该节点,整个右子树
的元素都应该大于该节点。例如:这意味着咱们须要在遍历树的同时保留结点的上界
与下界
,在比较时不只比较子结点的值,也要与上下界比较。数组
上述思路能够用递归法实现:
递归
进行该过程。public boolean isValidBST(TreeNode root) { return isValidBST(root, Long.MIN_VALUE, Long.MAX_VALUE); } private boolean isValidBST(TreeNode root, long min, long max){ if (root == null) { return true; } if (root.val <= min || root.val >= max){ return false; } return isValidBST(root.left, min, root.val) && isValidBST(root.right, root.val, max); } 复制代码
给定一个二叉搜索树,编写一个函数 kthSmallest
来查找其中第 k 个最小的元素。
说明: 你能够假设 k
老是有效的,1 ≤ k ≤ 二叉搜索树元素个数
。
示例 :
输入: root = [5,3,6,2,4,null,null,1], k = 3 5 / \ 3 6 / \ 2 4 / 1 输出: 3 复制代码
getCount
方法来获取传入节点的子节点数(包括本身)root
节点开始判断k值和子节点数的大小决定递归路径是往左仍是往右。public int kthSmallest(TreeNode root, int k) { if (root == null) { return 0; } int leftCount = getCount(root.left); if (leftCount >= k) { return kthSmallest(root.left, k); } else if (leftCount + 1 == k) { return root.val; } else { //注(1) return kthSmallest(root.right, k - leftCount - 1); } } private int getCount(TreeNode root) { if (root == null) { return 0; } return getCount(root.left) + getCount(root.right) + 1; } 复制代码
注: (1)为何是 k - leftCount - 1
而不是 k
,咱们能够把当前的二叉树当作左右两部分。在执行到这个条件的时候,很明显,左边 leftCount 个数,加上根节点,都小于所要求的元素。接着,如今要从右子树搜索,很明显,搜索是往下的,不可能往上(原根节点的方向)搜索,故,以前 leftCount + 1
个数做废,因此所传入 k - leftCount - 1
所谓双指针
指的是在遍历对象的过程当中,不是普通的使用单个指针进行访问,而是使用两个相同
方向或者相反
方向的指针进行扫描,从而达到相应的目的。
换言之,双指针
法充分使用了数组有序这一特征,从而在某些状况下可以简化一些运算。
给定一个非负数,表示一个数字数组,在该数的基础上+1,返回一个新的数组。该数字按照数位高低进行排列,最高位的数在列表的最前面。
示例 :
输入: [4,3,2,1] 输出: [4,3,2,2] 解释: 输入数组表示数字 4321。 复制代码
只须要判断
有没有进位并模拟出它的进位方式,如十位数加 11
个位数置为 00
,如此循环直到判断没有再进位就退出循环返回结果。
而后还有一些特殊状况就是当出现 999九、999999 之类的数字时,循环到最后也须要进位
,出现这种状况时须要手动
将它进一位。
给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一
public int[] plusOne(int[] digits) { for (int i = digits.length - 1; i >= 0; i--) { digits[i]++; digits[i] = digits[i] % 10; if (digits[i] != 0) return digits; } digits = new int[digits.length + 1]; digits[0] = 1; return digits; } 复制代码
给定一个数组和一个值,在原地删除与值相同的数字,返回新数组的长度。
index
用于记录新数组下标,遍历数组index++
并存入index
便可public int removeElement(int[] A, int elem) { if (A == null || A.length == 0) { return 0; } int index = 0; for (int i = 0; i < A.length; i++) { if (A[i] != elem) { A[index++] = A[i]; } } return index; } 复制代码
在原数组中“删除”重复出现的数字,使得每一个元素只出现一次,而且返回“新”数组的长度。
示例 :
给定 nums = [0,0,1,1,1,2,2,3,3,4], 函数应该返回新的长度 5, 而且原数组 nums 的前五个元素被修改成 0, 1, 2, 3, 4。 你不须要考虑数组中超出新长度后面的元素。 复制代码
size
和 i
,其中 size
是慢指针,而 i
是快指针。nums[size]
= nums[i]
,咱们就增长 i 以跳太重复项。nums[i]
=nums[size]
时,跳太重复项的运行已经结束(nums[i])
的值复制到 nums[size+1]
。i
接着咱们将再次重复相同的过程,直到 size
到达数组的末尾为止。public int removeDuplicates(int[] A) { if (A == null || A.length == 0) { return 0; } int size = 0; for (int i = 0; i < A.length; i++) { if (A[i] != A[size]) { A[++size] = A[i]; } } // (1) return size + 1; } 复制代码
注:由于 size 为下标,因此返回长度要加一
实现MyCalendar类来存储活动
。若是新添加的活动没有重复,则能够添加。类将有方法book(int start,int end)。这表明左闭右开的间隔[start,end)有了预约,范围内的实数x,都知足start <= x < end
,返回true。 不然,返回false,而且事件不会添加到日历中。
示例 :
MyCalendar(); MyCalendar.book(10, 20); // returns true MyCalendar.book(15, 25); // returns false MyCalendar.book(20, 30); // returns true 解释: 第一个日程安排能够添加到日历中. 第二个日程安排不能添加到日历中,由于时间 15 已经被第一个日程安排预约了。 第三个日程安排能够添加到日历中,由于第一个日程安排并不包含时间 20 。 复制代码
key-value
集合,它经过 红黑树 实现,继承于AbstractMap
,因此它是一个Map,即一个key-value集合。最大的key
,也可查询大于等于某个值的最小的key
。floorKey(K key)
方法用于返回小于或等于给定的键的全部键中,的最大键,或null
,若是不存在这样的键
ceilingKey(K key)
方法用于返回大于或等于返回到给定的键中,的最小键,或null
,若是不存在这样的键
class MyCalendar { TreeMap<Integer, Integer> calendar; MyCalendar() { calendar = new TreeMap(); } public boolean book(int start, int end) { Integer previous = calendar.floorKey(start), next = calendar.ceilingKey(start); if ((previous == null || calendar.get(previous) <= start) && (next == null || end <= next)) { calendar.put(start, end); return true; } return false; } } 复制代码
合并两个排序的整数数组A
和B
变成一个新
的数组。能够假设A具备足够的空间去添加B中的元素。
说明:
初始化 A 和 B 的元素数量分别为 m 和 n。 你能够假设 A 有足够的空间(空间大小大于或等于 m + n)来保存 B 中的元素。
示例:
输入: nums1 = [1,2,3,0,0,0], m = 3 nums2 = [2,5,6], n = 3 输出: [1,2,2,3,5,6] 复制代码
从后往前
追踪
添加元素的位置。public void mergeSortedArray(int[] A, int m, int[] B, int n) { int i = m - 1, j = n - 1, index = m + n - 1; while (i >= 0 && j >= 0) { if (A[i] > B[j]) { A[index--] = A[i--]; } else { A[index--] = B[j--]; } } while (i >= 0) { A[index--] = A[i--]; } while (j >= 0) { A[index--] = B[j--]; } } 复制代码
顾名思义,贪心算法老是做出在当前看来最好的选择。也就是说贪心算法并不从总体最优考虑,它所做出的选择只是在某种意义上的局部最优选择。固然,但愿贪心算法获得的最终结果也是总体最优的。虽然贪心算法不能对全部
问题都获得总体最优解,但对许多问题它能产生总体最优解。如单源最短路经问题,最小生成树问题等。在一些
状况下,即便贪心算法不能获得总体最优解
,其最终结果倒是最优解的很好近似。
视频
假设有一个数组,它的第i个元素是一支给定的股票在第i天的价格。若是你最多只容许完成一次交易(例如,一次买卖股票),设计一个算法来找出最大利润。
注意:
示例 :
输入: [7,1,5,3,6,4] 输出: 5 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 注意利润不能是 7-1 = 6, 由于卖出价格须要大于买入价格。 复制代码
若是将测试范例 [7, 1, 5, 3, 6, 4]
绘制成图,咱们发现:
min
和 profit
,它们分别对应迄今为止所获得的最小的谷值和最大的利润(卖出价格与最低价格之间的最大差值)。public int maxProfit(int[] prices) { if (prices == null || prices.length == 0) { return 0; } int min = Integer.MAX_VALUE; //记录最低的价格 int profit = 0; for (int price : prices) { min = Math.min(price, min); profit = Math.max(price - min, profit); } return profit; } 复制代码
给定一个数组 prices 表示一支股票天天的价格。能够完成任意次数的交易, 不过不能同时参与多个交易,设计一个算法求出最大的利润。
贪心:
public int maxProfit(int[] prices) { int profit = 0; for(int i = 0 ; i < prices.length -1; i++) { if(prices[i + 1] > prices[i]) { profit += prices[i + 1] - prices[i]; } } return profit; } 复制代码
给定一个整数数组,找到一个具备最大和的子数组,返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4], 输出: 6 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。 复制代码
public int maxSubArray(int[] nums) { if(nums == null || nums.length == 0) { return 0; } int max = Integer.MIN_VALUE; int sum = 0; for (int num : nums) { sum += num; max = Math.max(sum, max); sum = Math.max(sum, 0); } return max; } 复制代码
给定一个整型数组,找出主元素,它在数组中的出现次数严格大于数组元素个数的二分之一(能够假设数组非空,且数组中老是存在主元素)。
currentMajor
候选数 和 一个 count 用于记录次数currentMajor
值相同
时, count 值 +1
currentMajor
值不一样
时, count 值 -1
0
时,说明在以前访问的数组里 currentMajor 的数量小于或等于一半
赋值
为当前数,继续寻找。public int majorityNumber(List<Integer> nums) { int currentMajor = 0; int count = 0; for(Integer num : nums) { if(count == 0) { currentMajor = num; } if(num == currentMajor) { count++; } else { count--; } } return currentMajor; } 复制代码
本片文章篇幅总结越长。我一直以为,一片过长的文章,就像一堂超长的 会议/课堂,体验很很差,因此我打算再开一篇文章
在后续文章中,我将继续针对链表
栈
队列
堆
动态规划
矩阵
位运算
等近百种,面试高频算法题,及其图文解析 + 教学视频 + 范例代码
,进行深刻剖析有兴趣能够继续关注 _yuanhao 的编程世界
不求快,只求优质,每篇文章将以 2 ~ 3 天的周期进行更新,力求保持高质量输出
仓库地址:超级干货!精心概括视频、归类、总结
,各位路过的老铁支持一下!给个 Star !