该算法链接来源于
冲刺春招-精选笔面试 66 题大通关
第一版本的链接如下:
字节校园精选 66 道高频经典笔面试题(含多种思路)(上)
以下为我的学习笔记以及汇总,也为了方便其他人更加快速的浏览
题目:64. 最小路径和
给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
解释:因为路径 1→3→1→1→1 的总和最小。
示例 2:
输入:grid = [[1,2,3],[4,5,6]]
输出:12
提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 200
0 <= grid[i][j] <= 100
class Solution {
public int minPathSum(int[][] grid) {
// 初始化定义边界问题
if(grid == null || grid.length == 0 || grid[0].length == 0)return 0;
int m = grid.length;
int n = grid[0].length;
int[][] dp = new int[m][n];
dp[0][0] = grid[0][0];
for(int i = 1;i < m;i++){
dp[i][0] = dp[i - 1][0] + grid[i][0];
}
for(int j = 1;j < n;j++){
dp[0][j] = dp[0][j - 1] + grid[0][j];
}
for(int i = 1;i < m;i++){
for(int j = 1;j < n;j++){
dp[i][j] = Math.min(dp[i - 1][j],dp[i][j - 1]) + grid[i][j];
}
}
// 返回dp数组的最后一个值
return dp[m - 1][n - 1];
}
}
题目:300. 最长递增子序列
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
示例 1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
示例 2:
输入:nums = [0,1,0,3,2,3]
输出:4
示例 3:
输入:nums = [7,7,7,7,7,7,7]
输出:1
提示:
1 <= nums.length <= 2500
-104 <= nums[i] <= 104
进阶:
你能将算法的时间复杂度降低到 O(n log(n)) 吗?
class Solution {
public int lengthOfLIS(int[] nums) {
int n = nums.length;
int [] dp = new int[n];
// 将其整个dp数组置为1
Arrays.fill(dp,1);
int ans = 0;
// 核心算法是贪心,利用前面已经存储的值,使用前面的dp值
for(int i = 0;i < n;i++){
// 遍历每个元素前面所有的值
for(int j = 0;j < i;j++){
if(nums[i] > nums[j]){
// 如果有大于的值,则对应将其dp[j] + 1 与dp[i] 进行比较迭代
dp[i] = Math.max(dp[i],dp[j] + 1);
}
}
// 记录dp数组中最大的值
ans = Math.max(dp[i],ans);
}
return ans;
}
}
题目:bytedance-004. 机器人跳跃问题
机器人正在玩一个古老的基于 DOS 的游戏。游戏中有 N+1 座建筑——从 0 到 N 编号,从左到右排列。编号为 0 的建筑高度为 0 个单位,编号为 i 的建筑的高度为 H(i) 个单位。
起初, 机器人在编号为 0 的建筑处。每一步,它跳到下一个(右边)建筑。假设机器人在第 k 个建筑,且它现在的能量值是 E, 下一步它将跳到第个 k+1 建筑。它将会得到或者失去正比于与 H(k+1) 与 E 之差的能量。如果 H(k+1) > E 那么机器人就失去 H(k+1) - E 的能量值,否则它将得到 E - H(k+1) 的能量值。
游戏目标是到达第个 N 建筑,在这个过程中,能量值不能为负数个单位。现在的问题是机器人以多少能量值开始游戏,才可以保证成功完成游戏?
格式:
输入:
示例 1:
输入:
5
3 4 3 2 4
输出:4
示例 2:
输入:
3
4 4 4
输出:4
示例 3:
输入:
3
1 6 4
输出:3
提示:
1 <= N <= 105
1 <= H(i) <= 105
此题为找规律:
import java.util.*;
class Solution {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int[] nums = new int[N];
for (int i = 0; i < N; i++){
nums[i] = sc.nextInt();
}
// e=E+(E-h)
int sum = 0;
for (int i = N - 1; i >= 0; i--) {
sum = (sum + nums[i] + 1) / 2;// 向上取整所以加一
}
System.out.println(sum);
}
}
题目:
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。
请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。
示例 1:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。
示例 2:
输入:nums1 = [1], m = 1, nums2 = [], n = 0
输出:[1]
解释:需要合并 [1] 和 [] 。
合并结果是 [1] 。
示例 3:
输入:nums1 = [0], m = 0, nums2 = [1], n = 1
输出:[1]
解释:需要合并的数组是 [] 和 [1] 。
合并结果是 [1] 。
注意,因为 m = 0 ,所以 nums1 中没有元素。nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中。
提示:
nums1.length == m + n
nums2.length == n
0 <= m, n <= 200
1 <= m + n <= 200
-109 <= nums1[i], nums2[j] <= 109
进阶:你可以设计实现一个时间复杂度为 O(m + n) 的算法解决此问题吗?
思路:
将其使用双指针,类似链表遍历
一个个放进去,在使用数组进行重传返回即可
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int a=0,b=0;
int []sort=new int [m+n];
for(int i=0;i<m+n;i++){
if(a==m){
sort[i]=nums2[b++];
}else if (b==n){
sort[i]=nums1[a++];
}else if(nums1[a]>=nums2[b]){
sort[i]=nums2[b++];
}else if(nums1[a]<nums2[b]){
sort[i]=nums1[a++];
}
}
for (int i = 0; i != m + n; ++i) {
nums1[i] = sort[i];
}
}
}
此处有一个特殊性,上面的条件不可如下,因为nums1的数组后有为0的数组
错误代码展示:
if(nums1[a]>=nums2[b]){
sort[i]=nums2[b++];
}else if(nums1[a]<nums2[b]){
sort[i]=nums1[a++];
}else if(a==m){
sort[i]=nums2[b++];
}else if (b==n){
sort[i]=nums1[a++];
}
另外一种思路,为了最后不能重新赋值到另外一个数组
还是使用双指针,但是是从后往前放置即可
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int a = m - 1, b = n - 1;
int tail = m + n - 1;
for(;tail>=0;tail--) {
if (a == -1) {
nums1[tail] = nums2[b--];
} else if (b == -1) {
nums1[tail] = nums1[a--];
} else if (nums1[a] > nums2[b]) {
nums1[tail] = nums1[a--];
} else {
nums1[tail] = nums2[b--];
}
}
}
}
题目:31. 下一个排列
找出不满足数组逆序的
class Solution {
public void nextPermutation(int[] nums) {
int i = nums.length - 2;
while(i >= 0 && nums[i] >= nums[i + 1]){
i--;
}
if(i >= 0){
int j = nums.length - 1;
// 需要大于等于,否则交换的时候就是两个相同的元素在交换
while(j >= 0 && nums[i] >= nums[j]){
j--;
}
swap(nums,i,j);
}
// 从大到小进行排序,利用前后交换
reverse(nums, i + 1);
}
public void swap(int[] nums,int i,int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
public void reverse(int[] nums,int start){
int left = start;
int right = nums.length - 1;
while(left < right){
swap(nums,left,right);
left++;
right--;
}
}
}
题目:4. 寻找两个正序数组的中位数
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n)) 。
示例 1:
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
示例 2:
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
提示:
nums1.length == m
nums2.length == n
0 <= m <= 1000
0 <= n <= 1000
1 <= m + n <= 2000
-106 <= nums1[i], nums2[i] <= 106
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m = nums1.length;
int n = nums2.length;
int len = m + n;
// 初始化值为-1,对应如果为1的时候,
int left = 0,right = 0;
int aStart = 0,bStart = 0;
for(int i = 0;i <= len / 2;i++){
// 赋值每次遍历的right节点,比如1 ,2 。right为1,之后赋值给left,right为2,中位数为1 + 2
left = right;
// nums1数组不越界,而且nums2与nums1对比哪个值比较小,甚至nums2节点已经结束,也都放在nums1中
if(aStart < m && (n <= bStart || nums1[aStart] < nums2[bStart])){
right = nums1[aStart++];
}else {
right = nums2[bStart++];
}
// left在左,right在右,相邻一个节点
}
// 最后的结果单独判断是否是奇偶数
// 偶数
if((len & 1) == 0){
return (left + right) / 2.0;
// 奇数
}else {
return right;
}
}
}
题目:
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
示例 1:
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
示例 2:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。
提示:
1 <= prices.length <= 105
0 <= prices[i] <= 104
思路:
使用暴力遍历,求其最大的值的范围区间即可
(但是以下的方法显示超时,复杂度太高了)
class Solution {
public int maxProfit(int[] prices) {
int n=prices.length;
int bigprofit=0;
for(int i=0;i<n;i++){
for(int j=i+1;j<n;j++){
int ss=prices[j]-prices[i];
if(ss>bigprofit)bigprofit=ss;
}
}
return bigprofit;
}
}
在每一天最低点的时候,关注什么时候买入,卖出的话,只要比较比之前卖出的值要大就好
这部分的代码最主要的是还要判断之前的值else if(prices[i]-minprofit>maxprofit)
class Solution {
public int maxProfit(int[] prices) {
int n = prices.length;
// 获取最小值,对应在最小值的时候买入
int minprofit = Integer.MAX_VALUE;
// 获取最大与最小的差值,时刻存储这个最大利益
int maxprofit = 0;
for(int i = 0;i < n;i++){
// 获取最小值
if(minprofit > prices[i]){
minprofit = prices[i];
// 获取两者之间的最大值
}else if(prices[i] - minprofit > maxprofit){
maxprofit = prices[i] - minprofit;
}
}
return maxprofit;
}
}
题目:
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
示例 1:
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:
输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。
提示:
1 <= intervals.length <= 104
intervals[i].length == 2
0 <= starti <= endi <= 104
思路:
return new int[][]{};
Arrays.sort(intervals, (a,b)->{return a[0]-b[0] ;});
,或者是这种形式的Arrays.sort(intervals, (a,b)->a[0]-b[0] );
,或者如下:通过new Comparator()
Arrays.sort(intervals, new Comparator<int[]>(){
public int compare(int []a,int []b){
return a[0]-b[0];
}
});
return Arrays.copyOfRange(dp,0,index+1);
class Solution {
public int[][] merge(int[][] intervals) {
int n = intervals.length;
int[][] dp = new int[n][2];
Arrays.sort(intervals,(a,b) -> {return a[0] - b[0];});
// Arrays.sort(intervals,(a,b) -> (a[0] - b[0]));
// 需要定制数组的index下标
int index = 0;
int start = intervals[0][0];
int end = intervals[0][1];
for(int i = 1;i < n;i++){
if(intervals[i][0] <= end){
// 两者还要进行对比下谁最大
end = Math.max(intervals[i][1],end);
}else{
dp[index][0]=start;
dp[index][1]=end;
index++;
start=intervals[i][0];
end=intervals[i][1];
}
}
dp[index][0] = start;
dp[index][1] = end;
// 去除数组冗余的后缀
return Arrays.copyOfRange(dp,0,index + 1);
}
}
思路二:
通过列表进行实时添加以及更改,具体思路如下:
Listlist=new ArrayList<>();
list.toArray(new int[list.size()][]);
,注意size的大小class Solution {
public int[][] merge(int[][] intervals) {
if(intervals.length == 0){
return new int[][]{};
}
int n=intervals.length;
int dp[][]=new int [n][2];
Arrays.sort(intervals, new Comparator<int[]>(){
public int compare(int []a,int []b){
return a[0]-b[0];
}
});
List<int []>list=new ArrayList<>();
for(int i=0;i<n;i++){
int l=intervals[i][0],r=intervals[i][1];
if(list.size()==0||list.get(list.size()-1)[1]<l){
list.add(new int []{l,r});
}else{
list.get(list.size()-1)[1]=Math.max(list.get(list.size()-1)[1],r);
}
}
return list.toArray(new int[list.size()][]);
}
}
题目:135. 分发糖果
// 此题目为 相邻两个孩子评分更高的会获得更多的糖果
// 所以需要左边遍历的时候 右大于左。右边遍历的时候,左边大于右边
class Solution {
public int candy(int[] ratings) {
int[] candynum = new int[ratings.length];
// 设置一个初始化的数字
candynum[0] = 1;
// 可以通过将其整个数组置为1, Arrays.fill(candynum, 1);
// 从左往右遍历,如果左边大于右边,则多前面
for(int i = 1; i < ratings.length;i++){
if (ratings[i] > ratings[i - 1]){
// 不可直接设置为2,要通过前面的的进行贪心
// 一个比一个大,有可能层层递进
candynum[i] = candynum[i - 1] + 1;
}else {
candynum[i] = 1;
}
}
// 从右往前遍历,利用右边大于左边
for(int i = ratings.length - 2;i >= 0;i--){
if(ratings[i] > ratings[i + 1]){
// 既要满足左边以及右边,所以取左右的最大,而且从右往左遍历,左边先加1在进行比较
candynum[i] = Math.max(candynum[i],candynum[i + 1] + 1);
}
}
int ans = 0;
for (int s : candynum){
ans += s;
}
return ans;
}
}
题目:
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):
实现 MyQueue 类:
void push(int x) 将元素 x 推到队列的末尾
int pop() 从队列的开头移除并返回元素
int peek() 返回队列开头的元素
boolean empty() 如果队列为空,返回 true ;否则,返回 false
说明:
你 只能 使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
示例 1:
输入:
[“MyQueue”, “push”, “push”, “peek”, “pop”, “empty”]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]
解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false
提示:
1 <= x <= 9
最多调用 100 次 push、pop、peek 和 empty
假设所有操作都是有效的 (例如,一个空的队列不会调用 pop 或者 peek 操作)
进阶:
你能否实现每个操作均摊时间复杂度为 O(1) 的队列?换句话说,执行 n 个操作的总时间复杂度为 O(n) ,即使其中一个操作可能花费较长时间。
思路一:
虽然有两个栈,但一直保持只有一个栈的进出,比较折腾
特别注意的点是,pop出去的时候指针的变化要变回栈首元素
class MyQueue {
private Stack<Integer>;
private Stack<Integer> ;
private int front;
public MyQueue() {
s1 = new Stack<>();
s2 = new Stack<>();
}
public void push(int x) {
if(s1.empty()){
s1.push(x);
}
while(!s1.isEmpty()){
s2.push(s1.pop());
}
s1.push(x);
while(!s2.isEmpty()){
s1.push(s2.pop());
}
}
public void pop() {
s1.pop();
if(!s1.empty()){
front=s1.peek();
}
}
public int peek() {
return front;
}
public boolean empty() {
return s1.isEmpty();
}
}
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue obj = new MyQueue();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.peek();
* boolean param_4 = obj.empty();
*/
思路二:
用两个栈,一个栈主要是进,另外一个栈主要是pop和peek的操作
peek和pop都要经过一次判断
peek也要,毕竟单纯的push,没有pop操作,peek操作就一直为空
if(stk2.isEmpty()){
while(!stk1.isEmpty()){
stk2.push(stk1.pop());
}
}
完整代码如下:
class MyQueue {
Stack<Integer> stk1 ;
Stack<Integer> stk2;
public MyQueue() {
stk1 = new Stack<>();
stk2 = new Stack<>();
}
public void push(int x) {
stk1.push(x);
}
public int pop() {
// 判断是否栈2为空,如果为空,则对应将其栈1都输入进栈2
if(stk2.isEmpty()){
while(!stk1.isEmpty()){
stk2.push(stk1.pop());
}
}
return stk2.pop();
}
public int peek() {
if(stk2.isEmpty()){
while(!stk1.isEmpty()){
stk2.push(stk1.pop());
}
}
return stk2.peek();
}
public boolean empty() {
return stk1.isEmpty() && stk2.isEmpty();
}
}
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue obj = new MyQueue();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.peek();
* boolean param_4 = obj.empty();
*/
题目:
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例 1:
输入:n = 3
输出:[“((()))”,“(()())”,“(())()”,“()(())”,“()()()”]
示例 2:
输入:n = 1
输出:[“()”]
提示:
1 <= n <= 8
刚开始做这种类型的题目,思路逻辑可能没有那么清晰,具体可看官方题解:括号生成官方题解
思路一:
使用递归的方式存储,剩余左括号总数要小于等于右括号。 递归把所有符合要求的加上去就行了
列表中添加的字符串 generate("",n,n);
重心在l上,通过判断l的范围,如果l等于r,else(l
class Solution {
List<String>list=new ArrayList<>();
public List<String> generateParenthesis(int n) {
if(n<=0)return list;
generate("",n,n);
return list;
}
public void generate(String str,int l,int r){
if(l==0&&r==0){
list.add(str);
return ;
}
if(l==r){
generate(str+'(',l-1,r);
}else if (l<r){
if(l>0){
generate(str+'(',l-1,r);
}
generate(str+')',l,r-1);
}
}
}
另外一种思路也差不多
主要的判断在于l>0,r也要>0。而且如果出现l>r则进行返回上一层次,进行剪枝(可以优先使用这个方法)
class Solution {
List<String> list = new ArrayList<>();
public List<String> generateParenthesis(int n) {
if(n <= 0)return list;
generate("",n,n);
return list;
}
public void generate(String str,int left,int right){
// 如果left 或者right 同时为0,则进行list添加
if(left == 0 && right == 0){
list.add(str);
return ;
}
// 如果不满足括号原则,则进行剪枝
if(left > right){
return;
}
// left 如果大于0 则对应进行递归
if(left > 0){
generate(str + "(",left - 1,right);
}
if(right > 0){
generate(str + ")",left,right - 1);
}
}
}
以上的方式是通过左右括号都是n递减,也可以通过左右括号都是0,开始递增的方式进行判断
同样也是剪枝,具体不同点的方式如下:
class Solution {
List<String>list=new ArrayList<>();
public List<String> generateParenthesis(int n) {
if(n<=0)return list;
generate("",0,0,n);
return list;
}
public void generate(String str,int l,int r,int n){
if(l==n&&r==n){
list.add(str);
return ;
}
if(l<r){
return;
}
if(l<n){
generate(str+'(',l+1,r,n);
}
if(r<n){
generate(str+')',l,r+1,n);
}
}
}
题目:
给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
示例 1:
输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
示例 2:
输入:nums = [0,3,7,2,5,8,4,6,0,1]
输出:9
提示:
0 <= nums.length <= 105
-109 <= nums[i] <= 109
思路:
先去重可用set集合
遍历的时候如果x有,x+1,x+2等,但是可能会重复遍历
所以遍历的时候可以使用x,x-1进行验证
如果x-1没有的话,可以遍历x+1,寻找最长的尺寸,之后输出最长尺寸即可
class Solution {
public int longestConsecutive(int[] nums) {
// 使用HashSet一一添加
Set<Integer> set = new HashSet<>();
for(int num : nums){
set.add(num);
}
int count = 0;
// 遍历nums数组,对应判断前后是否在set集合中
for(int num : nums){
// 如果前一个不在hashset中,则进入判断,让其set从小到大进行判定
if(!set.contains(num - 1)){
// 初始化当前cur的值
int cur = 1;
while(set.contains(num + 1)){
cur += 1;
// 对应的num也要加1
num += 1;
}
count = Math.max(count,cur);
}
}
return count;
}
}
题目:129. 求根节点到叶节点数字之和
给你一个二叉树的根节点 root ,树中每个节点都存放有一个 0 到 9 之间的数字。
每条从根节点到叶节点的路径都代表一个数字:
例如,从根节点到叶节点的路径 1 -> 2 -> 3 表示数字 123 。
计算从根节点到叶节点生成的 所有数字之和 。
叶节点 是指没有子节点的节点。
输入:root = [1,2,3]
输出:25
解释:
从根到叶子节点路径 1->2 代表数字 12
从根到叶子节点路径 1->3 代表数字 13
因此,数字总和 = 12 + 13 = 25
输入:root = [4,9,0,5,1]
输出:1026
解释:
从根到叶子节点路径 4->9->5 代表数字 495
从根到叶子节点路径 4->9->1 代表数字 491
从根到叶子节点路径 4->0 代表数字 40
因此,数字总和 = 495 + 491 + 40 = 1026
提示:
树中节点的数目在范围 [1, 1000] 内
0 <= Node.val <= 9
树的深度不超过 10
class Solution {
public int sumNumbers(TreeNode root) {
int sum = 0;
return dfs(root,sum);
}
public int dfs(TreeNode root,int sum){
// 此处已经是临界值,为null的时候直接返回为0
if(root == null)return 0;
// 统计总值
sum = sum * 10 + root.val;
// 如果都为null,则直接返回sum。不用判断单个是否为null,本身root为null的时候会返回0
if(root.left == null && root.right == null){
return sum;
}else {
return dfs(root.left,sum) + dfs(root.right,sum);
}
}
}
层次遍历的逻辑:
class Solution {
public int sumNumbers(TreeNode root) {
if(root == null) return 0;
int sum = 0;
// 一个存储TreeNode 一个存储值
Queue<Integer> numque = new LinkedList<>();
Queue<TreeNode> nodeque = new LinkedList<>();
numque.offer(root.val);
nodeque.offer(root);
while(!nodeque.isEmpty()){
TreeNode node = nodeque.poll();
int n = numque.poll();
if(node.left == null && node.right == null){
sum += n;
}else{
// 此处并非是else if 而是两个if独立的条件
if(node.left != null){
nodeque.offer(node.left);
numque.offer(n * 10 + node.left.val);
}
if(node.right != null){
nodeque.offer(node.right);
numque.offer(n * 10 + node.right.val);
}
}
}
return sum;
}
}
题目:239. 滑动窗口最大值
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
示例 1:
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
示例 2:
输入:nums = [1], k = 1
输出:[1]
提示:
1 <= nums.length <= 105
-104 <= nums[i] <= 104
1 <= k <= nums.length
利用双端队列进行添加
// 与其队尾元素进行比较
// 队头元素是最大的,出的时候是出队尾元素
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
Deque<Integer> deque = new LinkedList<>();
// 将其第一个窗口都添加到队列中
for(int i = 0 ; i < k ;i++){
// 每次有新的元素之后 如果大于,就把新的队尾元素给剔除
while(!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]){
deque.pollLast();
}
// 否则进入到队尾元素
deque.offerLast(i);
}
// 定义剩下的数组元素
int[] res = new int [n - k + 1];
// 存储队头元素,因为队头元素一定最大 (一开始进入的时候就只有大于才能进入,否则会被剔除)
// 将其第一个队列中的头元素存到数组中
res[0] = nums[deque.peekFirst()];
for(int i = k;i < n;i++){
while(!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]){
deque.pollLast();
}
deque.offerLast(i);
// 由于上面添加的值,可能会超过窗口值,所以需要将其最大值(只关注最大值)
// 将其队头元素最大 且在区间外的,都把队头元素剔除
while(deque.peekFirst() <= i - k){
deque.pollFirst();
}
res[i - k + 1] = nums[deque.peekFirst()];
}
return res;
}
}
题目:bytedance-007. 化学公式解析
题目:
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
提示:
链表中节点的数目范围是 [0, 104]
-105 <= Node.val <= 105
pos 为 -1 或者链表中的一个 有效索引 。
进阶:你能用 O(1)(即,常量)内存解决此问题吗?
思路:
使用双指针遍历,但是每个指针不一样的步伐
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
if(head==null||head.next==null) return false;
ListNode p=head;
ListNode q=head.next;
while(p!=q){
if(q==null||q.next==null)return false;
p=p.next;
q=q.next.next;
}
return true;
}
}
或者两个指针的起点不一样,都是从头节点开始,注意其中的区别:
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
if(head==null||head.next==null) return false;
ListNode p=head;
ListNode q=head;
while(q!=null&&q.next!=null){
p=p.next;
q=q.next.next;
if(p==q)return true;
}
return false;
}
}
另一种写法:
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while(true){
if(fast == null || fast.next == null){
return false;
}
slow = slow.next;
fast = fast.next.next;
if(slow == fast)return true;
}
}
}
题目:
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
示例 1:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3 。
示例 2:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出:5
解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。
示例 3:
输入:root = [1,2], p = 1, q = 2
输出:1
提示:
树中节点数目在范围 [2, 105] 内。
-109 <= Node.val <= 109
所有 Node.val 互不相同 。
p != q
p 和 q 均存在于给定的二叉树中。
思路来源:236. 二叉树的最近公共祖先(DFS ,清晰图解)
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
//两个初始条件,要么为null,要么跟p或者q相等
//这是递归刚开始的判断
//判断其左右节点如果为null,则返回上去即为null
if(root==null)return null;
//如果判断的节点本身有一个相等,优先返回为自身。不用再去判断叶子节点
if(root==p||root==q)return root;
//上面的条件如果都不满足,则通过递归,后续遍历,所以先递归左节点,在递归右节点
TreeNode left=lowestCommonAncestor(root.left,p,q);
TreeNode right=lowestCommonAncestor(root.right,p,q);
//递归之后的判断,本身叶子节点为null,一开始的初始判断条件已经传回去了
//如果左边传回为null,代表这个值不相等或者为叶子节点。直接返回右节点的值。右节点同理
//如果左右都不想等,则返回的值为自身,也就是在2 7 4 这个子树中,2的左节点为7,右节点为4。则传回2回去即可
if(left==null)return right;
else if (right==null)return left;
else return root;
}
}
题目:92. 反转链表 II
给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。
示例 1
输入:head = [1,2,3,4,5], left = 2, right = 4
输出:[1,4,3,2,5]
示例 2:
输入:head = [5], left = 1, right = 1
输出:[5]
提示:
链表中节点数目为 n
1 <= n <= 500
-500 <= Node.val <= 500
1 <= left <= right <= n
总体的思路如下:
伪代码:
1 2 3 left-1 left 。。。。 right 。。。n
找到left - 1 假头节点
找到right + 1节点
反转left 到 right 节点
假头节点接上 right,left 接上 right + 1
class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
// 因为头节点有可能发生变化,使用虚拟头节点可以避免复杂的分类讨论
ListNode prehead = new ListNode(-1);
prehead.next = head;
ListNode leftNode = prehead;
// 第 1 步:从虚拟头节点走 left - 1 步,来到 left 节点的前一个节点
// 建议写在 for 循环里,语义清晰
for (int i = 0; i < left - 1; i++) {
leftNode = leftNode.next;
}
// 第 2 步:从 pre 再走 right - left + 1 步,来到 right 节点
ListNode rightNode = leftNode; // 指向第一个节点,leftNode,这个leftNode是第一部分断开的最后节点
for (int i = 0; i < right - left + 1; i++) {
rightNode = rightNode.next;
}
// 第 3 步:切断出一个子链表(截取链表)
ListNode temp1 = leftNode.next;
ListNode temp2 = rightNode.next;
// 注意:切断链接
leftNode.next = null;
rightNode.next = null;
// 第 4 步:同第 206 题,反转链表的子区间
reverseLinkedList(temp1);
// 第 5 步:接回到原来的链表中
leftNode.next = rightNode;
temp1.next = temp2;
return prehead.next;
}
private void reverseLinkedList(ListNode head) {
// 也可以使用递归反转一个链表
ListNode pre = null;
ListNode cur = head;
while (cur != null) {
ListNode next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
}
}
class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
ListNode prehead = new ListNode(-1);
prehead.next = head;
ListNode pre = prehead;
for(int i = 0;i < left - 1;i++){
pre = pre.next;
}
ListNode cur = pre.next;
ListNode next;
for(int i = 0;i < right - left;i++){
next = cur.next;
// 注意部分节点链表已经断开,使用这种方法
cur.next = next.next;
next.next = pre.next;
pre.next = next;
}
return prehead.next;
}
}
题目:322. 零钱兑换
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
示例 1:
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
示例 2:
输入:coins = [2], amount = 3
输出:-1
示例 3:
输入:coins = [1], amount = 0
输出:0
提示:
1 <= coins.length <= 12
1 <= coins[i] <= 231 - 1
0 <= amount <= 104
public class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];
// 将其所有数组值都变为最大值,因为求的是最小是
int max = amount + 1 ;
Arrays.fill(dp, max);
dp[0] = 0;
for (int i = 1; i <= amount; i++) {
for (int j = 0; j < coins.length; j++) {
// dp[i] 的值为临界值
// 主要是通过每个 dp[i - coins[j]] + 1 进行比较。前提是 i >= coins[j] ,否则数组越界溢出
if (coins[j] <= i) {
dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
}
}
}
// 返回最小硬币数量,数量比金额还大,说明无法组成
return dp[amount] > amount ? -1 : dp[amount];
}
}
题目:198. 打家劫舍
题目:
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 1:
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:
输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。
提示:
1 <= nums.length <= 100
0 <= nums[i] <= 400
思路:
一开始的初始条件有一个值的时候返回,以及没有值或者长度为0返回
通过动态规划
具体边界条件为一间屋和两间屋的区别
公式为相邻一间相加的最大值
class Solution {
public int rob(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int n = nums.length;
// 只有一个值的时候
if (n == 1) {
return nums[0];
}
int[] dp = new int[n];
// 贪心算法
dp[0] = nums[0];
//返回最大的那个,初始条件如果只有两个房屋,返回最大的那个
dp[1] = Math.max(dp[0],nums[1]);
for(int i = 2;i < n;i++){
dp[i] = Math.max(dp[i - 1],dp[i - 2] + nums[i]);
}
return dp[n - 1];
}
}
或者不存储其数组的值,存储在临时变量,具体如下:
class Solution {
public int rob(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int length = nums.length;
if (length == 1) {
return nums[0];
}
int first = nums[0], second = Math.max(nums[0], nums[1]);
for (int i = 2; i < length; i++) {
int temp = second;
second = Math.max(first + nums[i], second);
first = temp;
}
return second;
}
}
题目:bytedance-003. 古生物血缘远近判定
这道题类似编辑距离
DNA 是由 ACGT 四种核苷酸组成,例如 AAAGTCTGAC,假定自然环境下 DNA 发生异变的情况有:
基因缺失一个核苷酸
基因新增一个核苷酸
基因替换一个核苷酸
且发生概率相同。
古生物学家 Sam 得到了若干条相似 DNA 序列,Sam 认为一个 DNA 序列向另外一个 DNA 序列转变所需的最小异变情况数可以代表其物种血缘相近程度,异变情况数越少,血缘越相近,请帮助 Sam 实现获取两条 DNA 序列的最小异变情况数的算法。
格式:
输入:
示例:
输入:ACT,AGCT
输出:1
提示:
每个 DNA 序列不超过 100 个字符
import java.util.*;
public class Solution{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
String[] words = s.split(",");
int m = words[0].length();
int n = words[1].length();
int[][] dp = new int[m + 1][n + 1];
dp[0][0] = 0;
// word1删除元素,每删除一个,初始化都是下标删除的个数
for(int i = 1;i < m + 1;i++){
dp[i][0] = i;
}
for(int j = 1;j < n + 1;j++){
dp[0][j] = j;
}
for(int i = 1;i < m + 1;i++){
for(int j = 1;j < n + 1;j++){
//如果配对,则保存上一个的值
if(words[0].charAt(i - 1) == words[1].charAt(j - 1)){
dp[i][j] = dp[i - 1][j - 1];
}else {
//如果不配对要分三种情况,第一种是word1删除,第二种是word2删除,第三种是两者都删除。三者之间比较出最小值,记得每删除一个元素都是要加1
dp[i][j] = Math.min(dp[i - 1][j - 1],Math.min(dp[i - 1][j],dp[i][j - 1])) + 1;
}
}
}
System.out.println(dp[m][n]);
}
}
题目:
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
图示两个链表在节点 c1 开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
自定义评测:
评测系统 的输入如下(你设计的程序 不适用 此输入):
intersectVal - 相交的起始节点的值。如果不存在相交节点,这一值为 0
listA - 第一个链表
listB - 第二个链表
skipA - 在 listA 中(从头节点开始)跳到交叉节点的节点数
skipB - 在 listB 中(从头节点开始)跳到交叉节点的节点数
评测系统将根据这些输入创建链式数据结构,并将两个头节点 headA 和 headB 传递给你的程序。如果程序能够正确返回相交节点,那么你的解决方案将被 视作正确答案 。
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at ‘8’
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
输入:intersectVal = 2, listA = [1,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at ‘2’
解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [1,9,1,2,4],链表 B 为 [3,2,4]。
在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
示例 3:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null 。
提示:
listA 中节点数目为 m
listB 中节点数目为 n
1 <= m, n <= 3 * 104
1 <= Node.val <= 105
0 <= skipA <= m
0 <= skipB <= n
如果 listA 和 listB 没有交点,intersectVal 为 0
如果 listA 和 listB 有交点,intersectVal == listA[skipA] == listB[skipB]
进阶:你能否设计一个时间复杂度 O(m + n) 、仅用 O(1) 内存的解决方案?
思路:
错误思路:如果两个节点同时进行,为null的时候就会出错,用if判断在同时进行
正确思路:
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
//不要忘记加开始的判断条件
if(headA == null || headB == null)return null;
ListNode A1 = headA;
ListNode A2 = headB;
while(A1 != A2){
if(A1 != null){
A1 = A1.next;
}else{
A1 = headB;
}
if(A2 != null){
A2 = A2.next;
}else {
A2 = headA;
}
}
return A1;
}
}
题目:
给定一个单链表 L 的头节点 head ,单链表 L 表示为:
L0 → L1 → … → Ln - 1 → Ln
请将其重新排列后变为:
L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
输入:head = [1,2,3,4]
输出:[1,4,2,3]
示例 2:
输入:head = [1,2,3,4,5]
输出:[1,5,2,4,3]
提示:
链表的长度范围为 [1, 5 * 104]
1 <= node.val <= 1000
思路:
通过列表list存储各个值
之后通过list size的尺寸进行前后next即可
/*
一开始使用栈 以及在单独遍历,使用了两次遍历
使用ArrayList列表 有下标的思路,一个个存储
*/
class Solution {
public void reorderList(ListNode head) {
List<ListNode> list = new ArrayList<>();
// 最好不要使用head指针进行遍历,防止有些题目要用head
ListNode node = head;
while(node != null){
list.add(node);
node = node.next;
}
int i = 0;
// list最后一个位置,可以使用list.size()-1
int j = list.size() - 1;
while(i < j){
list.get(i).next = list.get(j);
i++;
list.get(j).next = list.get(i);
j--;
}
// 最后这个空 null的条件别忘了
list.get(i).next = null;
}
}
找中间节点
后段节点反转
合并链表:
class Solution {
public void reorderList(ListNode head) {
// 两个指针同时在第一步
ListNode slow = head;
ListNode fast = head;
// 找到中间节点
while(true){
// 如果遇到fast 为空或者fast.next为空,则break
if(fast == null || fast.next == null){
break;
}
slow = slow.next;
fast = fast.next.next;
}
// 输出的slow 为第一段节点的最后一个
// temp为第二段节点的开头
ListNode temp = slow.next;
// slow next节点为空,将其两段分开
slow.next = null;
// slow赋值放在temp第一个临时节点中
slow = temp;
// 对应将其后续的节点进行反转
// 定义一个空指针节点
ListNode head2 = null;
// 目前slow 以及 temp都在第二段的开头节点
while(slow != null){
// 先将其右序节点找到位置
temp = slow.next;
// 在断开右序节点,指到左边head2节点
slow.next = head2;
head2 = slow;
slow = temp;
}
// 目前head 以及head2 都是开头节点
ListNode cur1 = head;
ListNode cur2 = head2;
ListNode temp2 = null;
// 两者不为空的时候,因为两者是差不多长度
while(cur1 != null && cur2 != null){
// 记住右边的节点
temp2 = cur1.next;
cur1.next = cur2;
cur2 = cur2.next;
cur1.next.next = temp2;
cur1 = temp2;
// 不可这样,已经断链了
//cur2.next = temp;
//cur2 = cur2.next;
}
}
}
题目:
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。
提示:
链表中节点的数目范围在范围 [0, 104] 内
-105 <= Node.val <= 105
pos 的值为 -1 或者链表中的一个有效索引
进阶:你是否可以使用 O(1) 空间解决此题?
思路:
通过哈希表的遍历进行
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode node =head;
Set<ListNode>set=new HashSet<>();
while(node!=null){
if(set.contains(node)){
return node;
}else{
set.add(node);
}
//不要忘记这个节点的移动
node=node.next;
}
return null;
}
}
使用数学追击思想:
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
// 优先找到第一个相交的链表
while(true){
// fast为null 或者fast.next为null,直接返回null,防止本身就不是链表
if (fast == null || fast.next == null) return null;
fast = fast.next.next;
slow = slow.next;
if(fast == slow)break;
}
// 在同一速度进行 ,直到两个值相等
fast = head;
while(fast != slow){
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
题目:
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例 1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例 2:
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
提示:
你可以假设 nums 中的所有元素是不重复的。
n 将在 [1, 10000]之间。
nums 的每个元素都将在 [-9999, 9999]之间。
思路:
class Solution {
public int search(int[] nums, int target) {
int low = 0, high = nums.length - 1;
while (low <= high) {
int mid = (high - low) / 2 + low;
if (nums[mid]== target) {
return mid;
} else if (nums[mid] > target) {
high = mid - 1;
} else {
low = mid + 1;
}
}
return -1;
}
}
题目:
给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。
注意:不能使用任何内置的 BigInteger 库或直接将输入转换为整数。
示例 1:
输入: num1 = “2”, num2 = “3”
输出: “6”
示例 2:
输入: num1 = “123”, num2 = “456”
输出: “56088”
提示:
1 <= num1.length, num2.length <= 200
num1 和 num2 只能由数字组成。
num1 和 num2 都不包含任何前导零,除了数字0本身。
科普知识:
单个字符串的相加,具体lc链接如下:
415. 字符串相加
小知识点,字符串的相加,可以通过如下进行
class Solution {
public String addStrings(String num1, String num2) {
int l1=num1.length()-1,l2=num2.length()-1;
int add=0;
StringBuilder str=new StringBuilder();
while(l1>=0||l2>=0||add!=0){
//字符转换为数字
int x=l1>=0?num1.charAt(l1)-'0':0;
int y=l2>=0?num2.charAt(l2)-'0':0;
int sum=x+y+add;
//保存最后一个值
str.append(sum%10);
//将其进位添加到下一个值中
add=sum/10;
l1--;
l2--;
}
//字符串反转
str.reverse();
return str.toString();
}
}
同样,lc的二进制求和,也差不多一个概念意思
具体链接如下:67. 二进制求和
class Solution {
public String addBinary(String a, String b) {
int l1=a.length()-1,l2=b.length()-1;
int add=0;
StringBuilder str=new StringBuilder();
while(l1>=0||l2>=0||add!=0){
//字符转换为数字
int x=l1>=0?a.charAt(l1)-'0':0;
int y=l2>=0?b.charAt(l2)-'0':0;
int sum=x+y+add;
//保存最后一个值
str.append(sum%2);
//将其进位添加到下一个值中
add=sum/2;
l1--;
l2--;
}
//字符串反转
str.reverse();
return str.toString();
}
}
思路:
回到字符串的相乘,也就是多个字符串相乘 再相加:字符串相乘题解
对应通过累积的概念,以及数组进位以及保留位置
class Solution {
public String multiply(String num1, String num2) {
if(num1.equals("0") || num2.equals("0")){
return "0";
}
int m = num1.length();
int n = num2.length();
int[] arrs = new int[m + n];
for(int i = m - 1;i >= 0;i--){
int x = num1.charAt(i) - '0';
for(int j = n - 1;j >= 0;j--){
int y = num2.charAt(j) - '0';
// 类似卷积的思想
arrs[i + j + 1] += x * y;
}
}
// 处理对应的进位 以及 保留的位置在数组中
for(int i = m + n - 1;i > 0;i--){
arrs[i - 1] += arrs[i] / 10;
arrs[i] %= 10;
}
StringBuilder sb = new StringBuilder();
for(int i = 0;i < m + n ;i++){
// 字符串位数,前面可能为0,所以要先把0去除
if(arrs[i] == 0 && i == 0){
continue;
}
sb.append(arrs[i]);
}
return sb.toString();
}
}
题目:bytedance-002. 发下午茶
题目:
给你一个非负整数 x ,计算并返回 x 的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。
示例 1:
输入:x = 4
输出:2
示例 2:
输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842…, 由于返回类型是整数,小数部分将被舍去。
提示:
0 <= x <= 231 - 1
思路:
使用二分查找的思路,将其left设置为0,right设置为x。需要比较中间mid元素与x的大小关系
class Solution {
public int mySqrt(int x) {
int l = 0;
int r = x;
while(l <= r){
int mid = l + (r - l) / 2;
// 判断mid * mid 是否大于,如果小于,则l往右移动
if((long) mid * mid <= x){
l = mid + 1;
}else {
r = mid - 1;
}
}
return r;
}
}
题目:912. 排序数组
给你一个整数数组 nums,请你将该数组升序排列。
示例 1:
输入:nums = [5,2,3,1]
输出:[1,2,3,5]
示例 2:
输入:nums = [5,1,1,2,0,0]
输出:[0,0,1,1,2,5]
提示:
1 <= nums.length <= 5 * 104
-5 * 104<= nums[i] <= 5 * 104
快排:
class Solution {
public int[] sortArray(int[] nums) {
quickSort(nums,0,nums.length - 1);
return nums;
}
public void quickSort(int[] arr,int left,int right){
if(left > right){
return;
}
int i = left;
int j = right;
//temp就是基准位
int temp = arr[left];
// 直到i等于j的时候
while (i < j) {
//先看右边,依次往左递减
while (temp <= arr[j] && i<j) j--;
//再看左边,依次往右递增
while (temp>=arr[i]&&i<j) i++;
//如果满足条件则交换
if (i<j) {
int t = arr[j];
arr[j] = arr[i];
arr[i] = t;
}
}
//最后将基准为与i和j相等位置的数字交换
arr[left] = arr[i];
arr[i] = temp;
//递归调用左半数组
quickSort(arr, left, j-1);
//递归调用右半数组
quickSort(arr, j+1, right);
}
}
对于排序的其他思路,可看我这篇文章:【数据结构】常见排序算法详细分析(内含java与c++代码)
题目:887. 鸡蛋掉落
给你 k 枚相同的鸡蛋,并可以使用一栋从第 1 层到第 n 层共有 n 层楼的建筑。
已知存在楼层 f ,满足 0 <= f <= n ,任何从 高于 f 的楼层落下的鸡蛋都会碎,从 f 楼层或比它低的楼层落下的鸡蛋都不会破。
每次操作,你可以取一枚没有碎的鸡蛋并把它从任一楼层 x 扔下(满足 1 <= x <= n)。如果鸡蛋碎了,你就不能再次使用它。如果某枚鸡蛋扔下后没有摔碎,则可以在之后的操作中 重复使用 这枚鸡蛋。
请你计算并返回要确定 f 确切的值 的 最小操作次数 是多少?
示例 1:
输入:k = 1, n = 2
输出:2
解释:
鸡蛋从 1 楼掉落。如果它碎了,肯定能得出 f = 0 。
否则,鸡蛋从 2 楼掉落。如果它碎了,肯定能得出 f = 1 。
如果它没碎,那么肯定能得出 f = 2 。
因此,在最坏的情况下我们需要移动 2 次以确定 f 是多少。
示例 2:
输入:k = 2, n = 6
输出:3
示例 3:
输入:k = 3, n = 14
输出:4
提示:
1 <= k <= 100
1 <= n <= 104
双蛋智力题讲解 - b站
思路比较难,此处看的官方题解:
class Solution {
Map<Integer, Integer> memo = new HashMap<Integer, Integer>();
public int superEggDrop(int k, int n) {
return dp(k, n);
}
public int dp(int k, int n) {
if (!memo.containsKey(n * 100 + k)) {
int ans;
if (n == 0) {
ans = 0;
} else if (k == 1) {
ans = n;
} else {
int lo = 1, hi = n;
while (lo + 1 < hi) {
int x = (lo + hi) / 2;
int t1 = dp(k - 1, x - 1);
int t2 = dp(k, n - x);
if (t1 < t2) {
lo = x;
} else if (t1 > t2) {
hi = x;
} else {
lo = hi = x;
}
}
ans = 1 + Math.min(Math.max(dp(k - 1, lo - 1), dp(k, n - lo)), Math.max(dp(k - 1, hi - 1), dp(k, n - hi)));
}
memo.put(n * 100 + k, ans);
}
return memo.get(n * 100 + k);
}
}
题目:151. 反转字符串中的单词
给你一个字符串 s ,请你反转字符串中 单词 的顺序。
单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。
返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。
注意:输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。
示例 1:
输入:s = “the sky is blue”
输出:“blue is sky the”
示例 2:
输入:s = " hello world "
输出:“world hello”
解释:反转后的字符串中不能存在前导空格和尾随空格。
示例 3:
输入:s = “a good example”
输出:“example good a”
解释:如果两个单词间有多余的空格,反转后的字符串需要将单词间的空格减少到仅有一个。
提示:
1 <= s.length <= 104
s 包含英文大小写字母、数字和空格 ’ ’
s 中 至少存在一个 单词
使用API:
class Solution {
public String reverseWords(String s) {
// \s++ 可去除多余的空格
String[] ss = s.trim().split("\s++");
StringBuilder sb = new StringBuilder();
for(int i = 0;i < ss.length;i++){
if(i == 0){
sb.insert(0,ss[i]);
}else {
sb.insert(0,ss[i] + " ");
}
}
return sb.toString();
}
}
或者:
class Solution {
public String reverseWords(String s) {
// 除去开头和末尾的空白字符
s = s.trim();
// 正则匹配连续的空白字符作为分隔符分割
// 并将其转换为列表
List<String> wordList = Arrays.asList(s.split("\\s+"));
Collections.reverse(wordList);
return String.join(" ", wordList);
}
}
不用api自个实现:
class Solution {
public String reverseWords(String s) {
// 将其字符串的前后空格 以及 中间多个空格都删除了
StringBuilder sb = tirmSpace(s);
reverse(sb, 0 , sb.length() - 1);
reverseEachWord(sb);
return sb.toString();
}
public StringBuilder tirmSpace(String s){
int left = 0;
int right = s.length() - 1;
// 前后空格都删除
while(left <= right && s.charAt(left) == ' ')left++;
while(left <= right && s.charAt(right) == ' ')right--;
StringBuilder sb = new StringBuilder();
// 包括了right,所以此处有等于号
while(left <= right){
char c = s.charAt(left);
// sb.charAt(sb.length() - 1) != ' ',为了给每个单词中间加一个空格
if(c != ' ' || sb.charAt(sb.length() - 1) != ' '){
sb.append(c);
}
left++;
}
return sb;
}
public void reverse(StringBuilder sb,int left ,int right){
// 传入的right也是有包括,所以此处需要使用空格
while(left <= right){
char temp = sb.charAt(left);
// StringBuilder 字符之间的转换
sb.setCharAt(left++,sb.charAt(right));
sb.setCharAt(right--,temp);
}
}
public void reverseEachWord(StringBuilder sb){
int n = sb.length();
int start = 0;
int end = 0;
while(start < n){
// 每个单词之间的长度
while(end < n && sb.charAt(end) != ' '){
end++;
}
// 对应反转相对应的单词元素
reverse(sb,start,end - 1);
// 此处为单词的各个开头,end 以及start 一样。
start = end + 1;
end++;
}
}
}
题目:46. 全排列
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
示例 1:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
示例 2:
输入:nums = [0,1]
输出:[[0,1],[1,0]]
示例 3:
输入:nums = [1]
输出:[[1]]
提示:
1 <= nums.length <= 6
-10 <= nums[i] <= 10
nums 中的所有整数 互不相同
class Solution {
List<List<Integer>> list = new ArrayList<List<Integer>>();
LinkedList<Integer> sonlist = new LinkedList<>();
public List<List<Integer>> permute(int[] nums) {
boolean[] flags = new boolean[nums.length];
// 因为不重复,所以不用排序
backtrace(nums,flags);
return list;
}
public void backtrace(int[] nums,boolean[] flags){
if(sonlist.size() == nums.length){
// 此处要用new 产生的新对象,不然会被覆盖掉
list.add(new ArrayList<>(sonlist));
}
for(int i = 0;i < nums.length;i++){
// 提前为ture 表明判断过了,跳出循环
if(flags[i] == true)continue;
sonlist.addLast(nums[i]);
flags[i] = true;
backtrace(nums,flags);
sonlist.removeLast();
flags[i] = false;
}
}
}
定义的类型 不一样,代码有所差异:
class Solution {
List<List<Integer>> list = new ArrayList<List<Integer>>();
// 注意此处
List<Integer> sonlist = new ArrayList<>();
public List<List<Integer>> permute(int[] nums) {
boolean[] flags = new boolean[nums.length];
backtrace(nums,flags);
return list;
}
public void backtrace(int[] nums,boolean[] flags){
if(sonlist.size() == nums.length){
list.add(new ArrayList<>(sonlist));
}
for(int i = 0;i < nums.length;i++){
if(flags[i] == true)continue;
// 注意此处
sonlist.add(nums[i]);
flags[i] = true;
backtrace(nums,flags);
// 注意此处
sonlist.remove(sonlist.size() - 1);
flags[i] = false;
}
}
}
题目:2. 两数相加
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例 1:
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
示例 2:
输入:l1 = [0], l2 = [0]
输出:[0]
示例 3:
输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]
提示:
每个链表中的节点数在范围 [1, 100] 内
0 <= Node.val <= 9
题目数据保证列表表示的数字不含前导零
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
// 新建一个假头节点,主要为了return输出
ListNode prehead = new ListNode(-1);
ListNode pre = prehead;
// 定义carry进位
int carry = 0;
while(l1 != null || l2 != null){
int x = l1 == null ? 0 : l1.val;
int y = l2 == null ? 0 : l2.val;
int sum = x + y + carry;
// 将其进位保存着,留着下一位
carry = sum / 10;
sum %= 10;
// 此处要新建一个节点
pre.next = new ListNode(sum);
pre = pre.next;
// 不为null的时候才可往下一个节点靠
if(l1 != null)l1 = l1.next;
if(l2 != null)l2 = l2.next;
}
if(carry == 1){
pre.next = new ListNode(carry);
}
return prehead.next;
}
}