03.(set)数组中重复的数字
题目描述:找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3
思路:用HashSet记录出现过的数字
代码:
public int findRepeatNumber(int[] nums) {
Set<Integer> myNum = new HashSet<Integer>();
int repeat = -1;
for(int num:nums){
if(!myNum.add(num)){
repeat=num;
break;
}
}
return repeat;
}
04.(二维数组)二维数组中的查找
题目描述:在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
给定 target = 5,返回 true。
给定 target = 20,返回 false。
思路:
- 从左下角开始查找
- 若
nums[i][j]>targat
,i– - 若
nums[i][j]<targat
,j++
代码:
public static boolean findNumberIn2DArray(int[][] matrix, int target) {
int m = matrix.length;
if (m == 0) {
return false;
}
int n = matrix[0].length;
int i = m - 1, j = 0;
while (i >= 0 && j < n) {
if (matrix[i][j] > target) {
i--;
} else if (matrix[i][j] < target) {
j++;
} else {
return true;
}
}
return false;
}
05.(字符串)替换空格
题目描述:请实现一个函数,把字符串 s
中的每个空格替换成"%20"。
思路:
-
将字符换成char[],设置新的StringBuilder。
-
遇到空格,添加"%20"
-
否则添加原字符
代码:
public String replaceSpace(String s) {
StringBuilder newStr = new StringBuilder();
char[] chars = s.toCharArray();
for(char c:chars){
if(c==' '){
newStr.append("%20");
}else{
newStr.append(c);
}
}
return newStr.toString();
}
06.(栈)从尾到头打印链表
题目描述:输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
输入:head = [1,3,2]
输出:[2,3,1]
思路:
- 辅助栈调换顺序
代码:
public int[] reversePrint(ListNode head) {
Stack<ListNode> stack = new Stack<ListNode>();
ListNode tmp = head;
while(tmp!=null){
stack.push(tmp);
tmp=tmp.next;
}
int n = stack.size();
int []print= new int[n];
for(int i=0;i<n;i++){
tmp = stack.pop();
print[i] = tmp.val;
}
return print;
}
07.(树)重建二叉树【难】
题目描述:输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
3
/ \
9 20
/ \
15 7
思路:
- 构造map(value,中序index)
- 以前序【根左右】为基准划分
- 构造递归函数recur(root根【中】,left边界【中】,right边界【中】)
代码:
class Solution {
HashMap<Integer,Integer> indexInInorder = new HashMap<>();
int[] preorder;
public TreeNode buildTree(int[] preorder, int[] inorder) {
int n = inorder.length;
this.preorder=preorder;
for(int i=0;i<n;i++){
indexInInorder.put(inorder[i],i);
}
return recur(0,0,n-1);//(root根【前】,left边界【中】,right边界【中】)
}
TreeNode recur(int root,int left,int right){
if(left>right) return null;
TreeNode node = new TreeNode(preorder[root]);//值在preorder里
int i = indexInInorder.get(preorder[root]);//获取root在inorder里的位置左根右
node.left= recur(root+1,left,i-1);//左子树 根【前】:root+1 左【中】:left 右【中】:i-1
node.right=recur(root+i-left+1,i+1,right);//右子树 根【前】:root+(i-left)+1 左【中】:i+1 右【中】:right
return node;
}
}
09.(栈)用两个栈实现队列
题目描述:用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
输入:
["CQueue","appendTail","deleteHead","deleteHead"]
[[],[3],[],[]]
输出:[null,null,3,-1]
思路:
- 队列:先进先出
- 进队列:放入栈1
- 出队列:若栈2为空,将栈1取出来放进栈2,栈2顶为该出的
代码:
class CQueue {
Stack<Integer> stack1;
Stack<Integer> stack2;
public CQueue() {
stack1 = new Stack<>();
stack2 = new Stack<>();
}
public void appendTail(int value) {
stack1.push(value);
}
public int deleteHead() {
if(!stack2.isEmpty()){
return stack2.pop();
}else{
while(!stack1.isEmpty()){
stack2.push(stack1.pop());
}
}
if(!stack2.isEmpty()){
return stack2.pop();
}
return -1;
}
}
10-1.(dp)斐波那契数列
题目描述:写一个函数,输入 n
,求斐波那契(Fibonacci)数列的第 n
项(即 F(N)
)。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
输入:n = 2
输出:1
思路:
- dp[n] = dp[n-1]+dp[n-2]
代码:
public int fib(int n) {
int a = 0;
int b = 1;
int c;
for (int i = 0; i < n; i++) {
c = (a + b)%1000000007;
a = b;
b = c;
}
return a;
}
10-2.(dp)青蛙跳台阶问题
题目描述:一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
输入:n = 7
输出:21
思路:
- 和斐波拉契一样
- dp[n] = dp[n-1]+dp[n-2]
代码:
public int numWays(int n) {
int a = 1;
int b = 1;
int c;
for (int i = 0; i < n; i++) {
c = (a + b)%1000000007;
a = b;
b = c;
}
return a;
}
11.(二分)旋转数组的最小数字
题目描述:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。
输入:[3,4,5,1,2]
输出:1
思路:
- 二分查找
- 根据数字变化规律,缩小范围
代码:
public int minArray(int[] numbers) {
int n = numbers.length;
int low = 0, high = n-1,p;
while(low<high){
p = (low+high)/2;
if(numbers[p]<numbers[high]){
high = p;
}else if(numbers[p]>numbers[high]){
low = p+1;
}else{
high--;
}
}
return numbers[low];
}
12.(dfs)矩阵中的路径
题目描述:请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。
[[“a”,“b”,“c”,“e”],
[“s”,“f”,“c”,“s”],
[“a”,“d”,“e”,“e”]]
但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。
输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true
思路:
- 遍历每个位置作为起始点
- 记录查找到第k个数字,依次上下左右递归查找,直到返回true
代码:
class Solution {
char[] word;
int len;
public boolean exist(char[][] board, String word) {
this.word = word.toCharArray();
int n = board.length;
int m = board[0].length;
this.len = word.length();
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if(dfs(board,i,j,0)){
return true;
}
}
}
return false;
}
boolean dfs(char[][] board,int i, int j,int k) {
if(i<0||i>=board.length||j<0||j>=board[0].length||board[i][j]!=word[k]){
return false;
}
if(k==len-1){
return true;
}
char tmp = board[i][j];
board[i][j]='\0';//避免倒退
boolean res = dfs(board,i+1,j,k+1)||
dfs(board,i,j+1,k+1)||
dfs(board,i-1,j,k+1)||
dfs(board,i,j-1,k+1);
board[i][j] = tmp;
return res;
}
}
13.(dfs)机器人的运动范围
题目描述:地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?
输入:m = 2, n = 3, k = 1
输出:3
思路:
- 搜索+回溯
- vis数组记录是否访问过
代码:
class Solution {
public int movingCount(int m, int n, int k){
boolean[][] vis = new boolean[m][n];//vis数组记录访问过的格子
return dfs(vis,0,0,k,m,n);
}
public int dfs(boolean[][]vis,int i,int j,int k,int m,int n){
if(i<0||i>=m||j<0||j>=n||vis[i][j]||(count_sum(i)+count_sum(j)>k)){
return 0;//不满足要求不计入
}else{
vis[i][j]=true;
return 1+dfs(vis,i-1,j,k,m,n)+
dfs(vis,i,j-1,k,m,n)+
dfs(vis,i+1,j,k,m,n)+
dfs(vis,i,j+1,k,m,n);//1+上下左右递归
}
}
//计算符合规定吗
private int count_sum(int num){
int sum = num%10;
while(num/10!=0){
sum +=num/10;
num/=10;
}
return sum;
}
}
14-1.(dp)剪绳子
题目描述:给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
思路:
- 动态规划
- dp[i] = max(四种情况)
代码:
public int cuttingRope(int n) {
int[] dp = new int[n + 1];
dp[1] = 1;
for (int i = 2; i <= n; i++) {
for (int j = 1; j <= i / 2; j++) {
dp[i] = Math.max(dp[i], j * (i - j));
dp[i] = Math.max(dp[i], dp[j] * (i - j));
dp[i] = Math.max(dp[i], j * dp[i - j]);
dp[i] = Math.max(dp[i], dp[j] * dp[i - j]);
}
}
return dp[n];
}
14-2.(快速幂)剪绳子 II【难】
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m - 1] 。请问 k[0]k[1]…*k[m - 1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
代码:
public int cuttingRope(int n) {
if(n<=3) return n-1;
int b=n%3,p=1000000007;
long rem=1,x=3;
for(int a=n/3-1;a>0;a/=2){
if(a%2==1) rem=(rem*x)%p;
x=(x*x)%p;
}
if(b==0) return (int)(rem*3%p);
if(b==1) return (int)(rem*4%p);
return (int)(rem*6%p);
}
15.(bit)二进制中1的个数
题目描述:请实现一个函数,输入一个整数(以二进制串形式),输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。
代码:
public int hammingWeight(int n) {
int res=0;
while(n!=0){
res+=n&1;
n>>>=1;
}
return res;
}
16.(快速幂)数值的整数次方
题目描述:实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。
输入: 2.10000, 3
输出: 9.26100
代码:
public double myPow(double x, int n) {
if(x==0) return 0;
double y=x;
long b=n;//指数的绝对值
if(n<0){
y=1/x;//指数为负将底数求导
b=-b; //不能是b=-n,会溢出
}
double rem=1.0;
for(;b>0;b>>=1){
if((b&1)==1) rem*=y;//奇数的情况
y = y*y;//底数幂一次,指数除以2
}
return rem;
}
17.(数学)打印从1到最大的n位数
题目描述:输入数字 n
,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。
输入: n = 1
输出: [1,2,3,4,5,6,7,8,9]
代码:
public int[] printNumbers(int n) {
int max = (int)Math.pow(10,n)-1;//主要是这步
int[] ret = new int[max];
for(int i=0;i<max;i++){
ret[i]=i+1;
}
return ret;
}
18.(链表)删除链表的节点
题目描述:给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。
输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
代码:
public ListNode deleteNode(ListNode head, int val) {
ListNode fake = new ListNode(0);//新建一个链表头结点
fake.next = head;
ListNode cur = head;//两个指针
ListNode pre = fake;
while(cur!=null){
if(cur.val==val){//遇到目标跳过
pre.next = cur.next;
break;
}
pre = cur;
cur = cur.next;
}
return fake.next;
}
19.正则表达式匹配【难没做】
20.(词法)表示数值的字符串
题目描述:请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100"、“5e2”、"-123"、“3.1416”、"-1E-16"、“0123"都表示数值,但"12e”、“1a3.14”、“1.2.3”、"±5"及"12e+5.4"都不是。
class Solution {
private int index =0;
private boolean isUnsignedInteger(String s){//判断无符号整数
int sign = index;
while(s.charAt(index)<='9'&&s.charAt(index)>='0'){
index++;
}
return index>sign;
}
private boolean isInteger(String s){//判断整数
if(s.charAt(index)=='+'||s.charAt(index)=='-'){
index++;
}
return isUnsignedInteger(s);
}
public boolean isNumber(String s) {//判断数字
if(s==null||s.length()==0){
return false;
}
s = s+'/';
while(s.charAt(index)==' '){
index++;
}
boolean isCorrect = isInteger(s);
if(s.charAt(index)=='.'){
index++;
isCorrect = isUnsignedInteger(s)||isCorrect;
}
if(s.charAt(index)=='e'||s.charAt(index)=='E'){
index++;
isCorrect = isInteger(s)&&isCorrect;
}
while(s.charAt(index)==' '){
index++;
}
return s.charAt(index)=='/'&&isCorrect;
}
}
21.(快排)调整数组顺序使奇数位于偶数前面
题目描述:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
代码:
public int[] exchange(int[] nums) {
int size = nums.length;
int pre = 0, after = size - 1, tmp = 0;
while (pre < after) {//前后调换,类似快排
while (pre<size&&(nums[pre] % 2 == 1)) {
pre++;
}
while (after>=0&&nums[after] % 2 == 0) {
after--;
}
if(pre>=after){
break;
}
tmp = nums[after];
nums[after] = nums[pre];
nums[pre] = tmp;
pre++;
after--;
}
return nums;
}
22.(链表)链表中倒数第k个节点
题目描述:输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 2 个节点是值为 4 的节点。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode pre=head,after = head;
for(;k>0;k--){//一前一后,前面走k步,然后一起走
after = after.next;
}
while(after!=null){
after = after.next;
pre = pre.next;
}
return pre;
}
}
24.(链表)反转链表
题目描述:定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
代码:
public ListNode reverseList(ListNode head) {
ListNode pre=null,cur=head;//pre在前cur在后
while(cur!=null){
ListNode tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
25.(链表)合并两个排序的链表
题目描述:输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
代码:
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dom = new ListNode(0);
ListNode cur = dom;
while(l1!=null&&l2!=null){
if(l1.val<l2.val){
cur.next = l1;
l1=l1.next;
cur = cur.next;
}else{
cur.next = l2;
l2=l2.next;
cur = cur.next;
}
}
if(l1!=null){
cur.next = l1;
}
if(l2!=null){
cur.next = l2;
}
return dom.next;
}
26.(树)树的子结构
题目描述:输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即 A中有出现和B相同的结构和节点值。
例如:
给定的树 A:
3
/ \
4 5
/ \
1 2
给定的树 B:
4
/
1
返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。
public boolean isSubStructure(TreeNode A, TreeNode B) {
return (A!=null&&B!=null)&&(recur(A,B)||isSubStructure(A.left,B)||isSubStructure(A.right,B));//A,A的左子树,A的右子树
}
boolean recur(TreeNode A,TreeNode B){
if(B==null){//B的结构走完就true
return true;
}
if(A==null || A.val!=B.val){
return false;
}
return recur(A.left,B.left)&&recur(A.right,B.right);//左右结构相同
}
27.(树)二叉树的镜像
题目描述:请完成一个函数,输入一个二叉树,该函数输出它的镜像。
例如输入:
4
/ \
2 7
/ \ / \
1 3 6 9
镜像输出:
4
/ \
7 2
/ \ / \
9 6 3 1
输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]
代码:
public TreeNode mirrorTree(TreeNode root) {
if(root==null) return null;
TreeNode left = root.left;
root.left = mirrorTree(root.right);
root.right = mirrorTree(left);
return root;
}
28.(树)对称的二叉树
题目描述:请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
1
/ \
2 2
/ \ / \
3 4 4 3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
1
/ \
2 2
\ \
3 3
输入:root = [1,2,2,3,4,4,3]
输出:true
代码:
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root==null) return true;
return recur(root.left,root.right);
}
boolean recur(TreeNode L,TreeNode R){
if(L==null&&R==null) return true;
if(L==null||R==null) return false;
if(L.val!=R.val) return false;
return recur(L.left,R.right)&&recur(L.right,R.left);//左的右和右的左
}
}
29.(矩阵)顺时针打印矩阵
题目描述:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
代码:
public int[] spiralOrder(int[][] matrix) {
int n = matrix.length;
if(n==0) return new int[0];
int m = matrix[0].length;
int size = m * n;
int[] ret = new int[size];
int k = 0, i = -1, j = -1, min_n = 0, min_m = 0;
while (n > 0 || m > 0) {
for (i++, j++; j < m && k < size; j++, k++) {
ret[k] = matrix[i][j];
}
for (i++, j--; i < n && k < size; i++, k++) {
ret[k] = matrix[i][j];
}
for (i--, j--; j >= min_m && k < size; j--, k++) {
ret[k] = matrix[i][j];
}
for (i--, j++; i > min_n && k < size; i--, k++) {
ret[k] = matrix[i][j];
}
//设置好上下限
n--;
m--;
min_m++;
min_n++;
}
return ret;
}
30.(栈)包含min函数的栈
问题描述:定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.min(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.min(); --> 返回 -2.
代码:
class MinStack {
Stack<Integer> A, B;
/** initialize your data structure here. */
public MinStack() {
A = new Stack<>();
B = new Stack<>();//B保存一个递减栈
}
public void push(int x) {
A.add(x);
if(B.isEmpty()||B.peek()>=x){
B.add(x);
}
}
public void pop() {
if(A.peek().equals(B.peek())){
B.pop();
}
A.pop();
}
public int top() {
return A.peek();
}
public int min() {
return B.peek();
}
}
39.(数学)数组中出现次数超过一半的数字
题目描述:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
输出: 2
思路:
- 摩尔投票法: 核心理念为 票数正负抵消 。
- 推论一: 若记 众数 的票数为 +1,非众数 的票数为 -1 ,则一定有所有数字的 票数和 >0
- 推论二: 若数组的前 a 个数字的 票数和 = 0 ,则 数组剩余 (n-a) 个数字的 票数和一定仍 >0 ,即后 (n-a)个数字的 众数仍为 x 。
代码:
public int majorityElement(int[] nums) {
int x = 0, votes = 0;
for (int num : nums) {
if (votes == 0) {
x = num;
}
votes += num == x ? 1 : -1;
}
return x;
}
40.最小的k个数【换个方法】
题目描述:输入整数数组 arr
,找出其中最小的 k
个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]
代码:
public int[] getLeastNumbers(int[] arr, int k) {
int[] ans = new int[k];
Arrays.sort(arr);//排序
for(int i=0;i<k;i++){
ans[i] = arr[i];
}
return ans;
}
41.数据流中的中位数
42.(dp)连续子数组的最大和
题目描述:输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n)。
输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
代码:
public int maxSubArray(int[] nums) {
int max = nums[0];
for(int i=1;i<nums.length;i++){
nums[i] += nums[i-1]<0?0:nums[i-1];
max = Math.max(max,nums[i]);
}
return max;
}
43
44.(数学)数字序列中某一位的数字
数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。
请写一个函数,求任意第n位对应的数字。
输入:n = 11
输出:0
代码:
public int findNthDigit(int n) {
long start = 1, count = 9;
int digit = 1;
while (n > count) {
n -= count;
start *= 10;
digit++;
count = digit * start * 9;
}
long num = start + (n - 1) / digit;
return Long.toString(num).charAt((n - 1) % digit) - '0';
}
45
46.(dp)把数字翻译成字符串
题目描述:给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"
思路:
-
动态规划
-
转移方程
dp[n] = dp[n-1] ; s[i-1]s[i]不构成字母(10>s[i-1]s[i]&&s[i-1]s[i]>25)
dp[n] = dp[n-2]+dp[n-1] ; s[i-1]s[i]构成字母(10<s[i-1]s[i]<25)
-
初始值
- dp[0]=dp[1]=1
代码:
public int translateNum(int num) {
String str = Integer.toString(num);
int len = str.length();
int[] dp = new int[len+1];
dp[0] = dp[1] = 1;
for (int i = 2; i <= len; i++) {
String tmp = str.substring(i - 2, i);
dp[i] = tmp.compareTo("10") >= 0
&& tmp.compareTo("25") <= 0 ? dp[i - 2] + dp[i - 1] : dp[i - 1];
}
return dp[len];
}
47.(dp)礼物的最大价值
题目描述:在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?
输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 12
解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物
思路:
- 动态规划
- 转移方程
dp[i][j]=max(dp[i-1][j]+dp[i][j-1])+s[i][j]
- 初始值
dp[i][0]=dp[0][j]=0
代码:
public int maxValue(int[][] grid) {
int m = grid.length, n = grid[0].length;
int[][] dp = new int[m + 1][n + 1];
for (int i = 0; i < m; i++) {
dp[i][0] = 0;
}
for (int i = 0; i < n; i++) {
dp[0][i] = 0;
}
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1])+grid[i-1][j-1];
}
}
return dp[m][n];
}
改进:
public int maxValue(int[][] grid) {
int m = grid.length, n = grid[0].length;
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
if(i == 0 && j == 0) continue;
if(i == 0) grid[i][j] += grid[i][j - 1] ;
else if(j == 0) grid[i][j] += grid[i - 1][j];
else grid[i][j] += Math.max(grid[i][j - 1], grid[i - 1][j]);
}
}
return grid[m - 1][n - 1];
}
48.(dp)最长不含重复字符的子字符串
题目描述:请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
思路:
-
动态规划
-
转移方程
-
用map记录s[j]前最近一次出现相同字符的下标i
-
dp[j] = dp[j-1]+1 ; i不存在||dp[j-1]<j-i
-
dp[j] = j-i ;其它
-
-
初始值
- dp[0]=0
-
max记录dp[i]的最大值
代码:
public int lengthOfLongestSubstring(String s) {
HashMap<Character,Integer> map = new HashMap<>();
int[]dp = new int[s.length()+1];
char [] chars = s.toCharArray();
int max =0;
dp[0]=0;
for(int i=1;i<dp.length;i++){
int j = map.getOrDefault(chars[i-1],-1);
if(j==-1||(i-j)>dp[i-1]){
dp[i]=dp[i-1]+1;
}else{
dp[i] = i-map.get(chars[i-1]);
}
max = Math.max(max,dp[i]);
map.put(chars[i-1],i);
}
return max;
}
49.(dp)丑数
题目描述:我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
思路:
-
动态规划
-
用dp[i]记录第i个丑数,dp[i]一定是dp[j]*2,dp[j]*3,dp[j]*5得来的。从前向后,保证从大到小的顺序。
-
递推式
dp[i] = min(dp[index_2]*2,dp[index_3]*3,dp[index_5]*5)
dp[index_2]表示前index_2-1的dp[i]已经乘过2了
-
初始值 dp[0]=1
代码:
public int nthUglyNumber(int n) {
int[] dp = new int[n];
dp[0] = 1;
int index_2=0,index_3=0,index_5=0;
for(int i=1;i<n;i++){
int ans_2=dp[index_2]*2,ans_3= dp[index_3]*3,ans_5=dp[index_5]*5;
int min = Math.min(Math.min(ans_2,ans_3),ans_5);
index_2+=ans_2==min?1:0;
index_3+=ans_3==min?1:0;
index_5+=ans_5==min?1:0;
dp[i] = min;
}
return dp[n-1];
}
50.(hash)第一个只出现一次的字符
**问题描述:**在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。
s = "abaccdeff"
返回 "b"
s = ""
返回 " "
思路:
- 第一次遍历,用hashmap记录是否出现过
- 第二次遍历,输出第一个只出现过一次的字符
代码:
public char firstUniqChar(String s) {
char[] chars = s.toCharArray();
Map<Character,Boolean> map =new HashMap<>();
for(char c:chars){
map.put(c,!map.containsKey(c));
}
for(char c:chars){
if(map.get(c)){
return c;
}
}
return ' ';
}
51.(归并)数组中的逆序对
题目描述:在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
输入: [7,5,6,4]
输出: 5
思路:
在归并排序的过程中记录逆序对数
代码:
public int reversePairs(int[] nums) {
int n = nums.length;
if(n<2){
return 0;
}
int[]tmp = new int[n];
return countReverse(nums,tmp,0,n-1);
}
private int countReverse(int nums[],int []tmp,int left,int right){
if(left==right){
return 0;
}
int mid = left + (right-left)/2;
int leftAns = countReverse(nums,tmp,left,mid);
int rightAns = countReverse(nums,tmp,mid+1,right);
if(nums[mid]<=nums[mid+1]){
return leftAns+rightAns;
}
return leftAns+rightAns+mergeReverse(nums,tmp,left,mid,right);
}
private int mergeReverse(int nums[],int []tmp,int left,int mid,int right){
for(int i=left;i<=right;i++){
tmp[i]=nums[i];
}
int i=left,j=mid+1,k,cnt=0;
for(k=left;k<=right;k++){
if(i==mid+1){
nums[k]=tmp[j];
j++;
}else if(j==right+1){
nums[k]=tmp[i];
i++;
}
else if(tmp[i]<=tmp[j]){
nums[k]=tmp[i];
i++;
}else{
nums[k] = tmp[j];
j++;
cnt+=mid-i+1;
}
}
return cnt;
}
52.(链表)两个链表的第一个公共节点
题目描述:输入两个链表,找出它们的第一个公共节点。
思路:
- 同时遍历两个链表,当一个链表走到null时,返回到开头(消除长度差)
- 接着遍历,当它们相遇则为第一个公共节点
代码:
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode ptrA = headA;
ListNode ptrB = headB;
while(ptrA!=ptrB){
ptrA = ptrA==null? headA:ptrA.next;
ptrB = ptrB==null? headB:ptrB.next;
}
return ptrA;
}
53-1.(二分)在排序数组中查找数字 I
题目描述:统计一个数字在排序数组中出现的次数。
输入: nums = [5,7,7,8,8,10], target = 8
输出: 2
思路:
- 排序数组二分查找
代码:
public int search(int[] nums, int target) {
return binary(nums,target,0,nums.length-1);
}
private int binary(int[]nums,int target,int left,int right){
if(nums.length==0){
return 0;
}
if(nums[left]>target||nums[right]<target){
return 0;
}
if(left==right&&nums[left]==target){
return 1;
}
int mid = left+(right-left)/2;
return binary(nums,target,left,mid)+binary(nums,target,mid+1,right);
}
53-2.(二分)0~n-1中缺失的数字
题目描述:一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
输入: [0,1,3]
输出: 2
思路:
- 二分查找,不缺数字的话数组长度等于数字差
代码:
class Solution {
int miss=-1;
public int missingNumber(int[] nums) {
int n=nums.length;
if(n-1==nums[n-1]-nums[0]){//初始情况,若数组有序,缺的在两端
if(nums[0]!=0) return 0;
return nums[n-1]+1;
}
findNumber(nums,0,nums.length-1);
return miss;
}
void findNumber(int[]nums,int left,int right){
if(nums.length==0||nums[right]-nums[left]==right-left){
return;
}
int mid=left+(right-left)/2;
if(nums[mid]+1!=nums[mid+1]){
this.miss=nums[mid]+1;
return;
}else{
findNumber(nums,left,mid);
findNumber(nums,mid+1,right);
}
}
}
54.(树)二叉搜索树的第k大节点
题目描述:给定一棵二叉搜索树,请找出其中第k大的节点。
输入: root = [3,1,4,null,2], k = 1
3
/ \
1 4
\
2
输出: 4
思路:
- 右根左遍历,记录第几个
代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
int ans;
int k;
boolean flag=false;
public int kthLargest(TreeNode root, int k) {
this.k=k;
myTree(root);
return this.ans;
}
private void myTree(TreeNode root){
if(root==null||flag) return;
myTree(root.right);
if(--this.k==0){
this.ans = root.val;
this.flag = true;
return;
}
myTree(root.left);
}
}
55-1.(树)二叉树的深度
输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。
例如:
给定二叉树 [3,9,20,null,null,15,7]
,
3
/ \
9 20
/ \
15 7
返回它的最大深度 3 。
思路:
- 先序遍历
代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
int max=-1;
public int maxDepth(TreeNode root) {
findDepth(root,0);
return this.max;
}
private void findDepth(TreeNode root,int height){
if(root==null){
if(height>this.max){
this.max=height;
}
return;
}
findDepth(root.left,height+1);
findDepth(root.right,height+1);
}
}
55-2.(树)平衡二叉树
题目描述:输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。
给定二叉树 [3,9,20,null,null,15,7]
3
/ \
9 20
/ \
15 7
返回 true
。
思路:
- 树的高度=Max(左子树的高度,右子树的高度)+1
- 从底向上(后序遍历),计算左右子树高度
- 平衡:返回树的高度
- 不平衡:返回-1剪枝
代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isBalanced(TreeNode root) {
return recur(root)!=-1;
}
private int recur(TreeNode root){
if(root==null) return 0;
int left = recur(root.left);
if(left==-1) return -1;
int right = recur(root.right);
if(right==-1) return -1;
return Math.abs(left-right)<2?Math.max(left,right)+1:-1;
}
}
56-1.(位) 数组中数字出现的次数
一个整型数组 nums
里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]
思路:
- 分组异或
- 两个相同的数异或为0,则nums所有数异或结果all为两个只出现一次的数的异或结果
- all为1的位为两个数字不相同的位,能够将所有数字分为两组,两个不相同的数字各在一组,相同的数字在同一组
代码:
public int[] singleNumbers(int[] nums) {
int all = 0;
for(int x:nums){
all^=x;
}
int flag=1;
while((flag&all)==0){
flag<<=1;
}
int a1=0,a2=0;
for(int x:nums){
if((flag&x)==0){
a1^=x;
}else{
a2^=x;
}
}
return new int[]{a1,a2};
}
56-2.(位)数组中数字出现的次数 II
在一个数组 nums
中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
输入:nums = [3,4,3,3]
输出:4
思路:
- 法1:状态机,虽然写出来很简单但是推导很麻烦
- 法2:遍历每一位统计每一位出现的次数,除一个数字外,其余数字都出现m次。用每一位的总数%m,为1的则那一位为1
代码:
public int singleNumber(int[] nums) {
int[] bits =new int[32];
for(int x:nums){
for(int i=0;i<32;i++){
bits[i]+=(x&1);
x>>>=1;
}
}
int m=3,ans = 0;
for(int i=31;i>=0;i--){
ans<<=1;
ans|=bits[i]%3;
}
return ans;
}
57.(双指针)和为s的两个数字
题目描述:输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。
输入:nums = [2,7,11,15], target = 9
输出:[2,7] 或者 [7,2]
思路:
- 双指针,分别在数组两边
- 如nums[i]+num[j]>target:j–
- 如nums[i]+nums[j]<target:i++
- 直到相等
- 状态 S(i, j)S(i,j) 切换至 S(i + 1, j)S(i+1,j) ,则会消去一行元素,相当于 消去了状态集合 {S(i, i + 1), S(i, i + 2), …, S(i, j - 2), S(i, j - 1), S(i, j)S(i,i+1),S(i,i+2),…,S(i,j−2),S(i,j−1),S(i,j) } 。(由于双指针都是向中间收缩,因此这些状态之后不可能再遇到)。
- 由于 numsnums 是排序数组,因此这些 消去的状态 都一定满足 S(i, j) < targetS(i,j)<target ,即这些状态都 不是解 。
- 结论: 以上分析已证明 “每次指针 ii 的移动操作,都不会导致解的丢失” ,即指针 ii 的移动操作是 安全的 ;同理,对于指针 jj 可得出同样推论;因此,此双指针法是正确的。
代码:
public int[] twoSum(int[] nums, int target) {
int i=0,j=nums.length-1;
while(i!=j){
int tmp=nums[i]+nums[j];
if(tmp==target){
return new int[]{nums[i],nums[j]};
}else if(tmp>target){
j--;
}else{
i++;
}
}
return new int[0];
}
57-2.(双指针)和为s的连续正数序列
题目描述:输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
输入:target = 9
输出:[[2,3,4],[4,5]]
思路:
- 双指针,滑动窗口
代码:
public int[][] findContinuousSequence(int target) {
List<int[]> ans = new ArrayList<>();
int i=1,j=2,sum=3;
while(i<j){
if(sum==target){
int[]tmp = new int[j-i+1];
for(int k=i;k<=j;k++){
tmp[k-i]=k;
}
ans.add(tmp);
}
if(sum<target){
j++;
sum+=j;
}else{
sum-=i;
i++;
}
}
return ans.toArray(new int[0][]);
}
58-1.(双指针)翻转单词顺序
题目描述:输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student. “,则输出"student. a am I”。
输入: " hello world! "
输出: "world! hello"
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
思路:
- 双指针,定位单词首尾
代码:
public String reverseWords(String s) {
s = s.trim();//去首尾空格
int i = s.length()-1,j=i;
StringBuilder ans = new StringBuilder();
while(i>=0){
while(i>=0&&s.charAt(i)!=' ') i--;//找到单词首
ans.append(s.substring(i+1,j+1)+" ");
while(i>=0&&s.charAt(i)==' ') i--;//找到新的单词尾
j=i;
}
return ans.toString().trim();//去首尾空格
}
58-2.(字符串)左旋转字符串
题目描述:字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。
代码:
public String reverseLeftWords(String s, int n) {
StringBuilder ans = new StringBuilder();
for(int i=n;i<n+s.length();i++){
ans.append(s.charAt(i%s.length()));
}
return ans.toString();
}
59-1.(队列)滑动窗口的最大值
给定一个数组 nums
和滑动窗口的大小 k
,请找出所有滑动窗口里的最大值。
输入: 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
思路:
- 单调队列
代码:
public int[] maxSlidingWindow(int[] nums, int k) {
if(nums.length==0||k==0){
return new int[0];
}
Deque<Integer>deque = new LinkedList<>();
int[] ans = new int[nums.length-k+1];
for(int j=0,i=1-k;j<nums.length;i++,j++){
if(i>0&&nums[i-1]==deque.peekFirst()){
deque.removeFirst();# 判断移除的是不是刚好是队列最大值
}
while(!deque.isEmpty()&&deque.peekLast()<nums[j]){
deque.removeLast();# 保证队列的单调性
}
deque.addLast(nums[j]);
if(i>=0){
ans[i] = deque.peekFirst();
}
}
return ans;
}
59-2.(队列)队列的最大值
题目描述:请定义一个队列并实现函数 max_value
得到队列里的最大值,要求函数max_value
、push_back
和 pop_front
的均摊时间复杂度都是O(1)。
若队列为空,pop_front
和 max_value
需要返回 -1
["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]
思路:
- 单调队列
代码:
class MaxQueue {
Queue<Integer> queue;
Deque<Integer> deque;
public MaxQueue() {
queue = new LinkedList<>();
deque = new LinkedList<>();
}
public int max_value() {
return queue.isEmpty()?-1:deque.peekFirst();
}
public void push_back(int value) {
while(!deque.isEmpty()&&value>deque.peekLast()){
deque.removeLast();
}
deque.addLast(value);
queue.add(value);
}
public int pop_front() {
if(queue.isEmpty()){
return -1;
}
if(queue.peek().equals(deque.peekFirst())){
deque.removeFirst();
}
return queue.remove();
}
}
60.(dp)n个骰子的点数
题目描述:把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。
你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。
输入: 1
输出: [0.16667,0.16667,0.16667,0.16667,0.16667,0.16667]
输入: 2
输出: [0.02778,0.05556,0.08333,0.11111,0.13889,0.16667,0.13889,0.11111,0.08333,0.05556,0.02778]
思路:
-
动态规划
-
转移方程
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ECDv0gwM-1612416419529)(C:\Users\53564\AppData\Roaming\Typora\typora-user-images\image-20210131120321031.png)]
-
初始值
dp[1][1]~dp[1][6]=1
代码:
public double[] dicesProbability(int n) {
int[][]dp = new int[n+1][6*n+1];
for(int i=1;i<=6;i++){
dp[1][i] = 1;
}
for(int i=2;i<=n;i++){
for(int j=i;j<=6*i;j++){
for(int k=1;k<=6&&k<j;k++){
dp[i][j] += dp[i-1][j-k];
}
}
}
double[] ans = new double[5*n+1];
for(int i=n;i<=6*n;i++){
ans[i-n] = ((double)dp[n][i])/(Math.pow(6,n));
}
return ans;
}
61.(找规律)扑克牌中的顺子
题目描述:从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。
输入: [1,2,3,4,5]
输出: True
思路:
代码:
public boolean isStraight(int[] nums) {
Set<Integer> repeat = new HashSet<>();
int min=14,max=0;
for(int x:nums){
if(x==0) continue;
max = Math.max(max,x);
min = Math.min(min,x);
if(repeat.contains(x)) return false;
repeat.add(x);
}
return max-min<5;
}
62.(找规律)圆圈中最后剩下的数字
题目描述:0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。
例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。
思路:
上一轮index = (当前index + m) % 上一轮剩余数字的个数
代码:
public int lastRemaining(int n, int m) {
int ans=0;
for(int i=2;i<=n;i++){
ans = (ans+m)%i;
}
return ans;
}
63.(dp)股票的最大利润
题目描述:假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
思路:
-
动态规划
-
转移方程
-
dp[i] = max(dp[i-1],price[i]-min[i])
-
第i天的最大利润等于
- 第i-1天的最大利润
- 第i天卖出,减前i-1天最小值买入
的最大值
-
-
初始化:dp[0]=0
代码:
public int maxProfit(int[] prices) {
int dayMin=Integer.MAX_VALUE,ans=0;
for(int x:prices){
dayMin = Math.min(dayMin,x);
ans = Math.max(ans,x-dayMin);
}
return ans;
}
64.(逻辑符短路)求1+2+…+n
题目描述:求 1+2+...+n
,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
输入: n = 3
输出: 6
思路:
-
boolean x = n>1 && sumNums(n-1)>0;
- n>1作为开启递归的条件
代码:
class Solution {
int ans = 0;
public int sumNums(int n) {
boolean x = n>1 && sumNums(n-1)>0;
ans+=n;
return ans;
}
}
65.(位)不用加减乘除做加法
题目描述:写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。
输入: a = 1, b = 1
输出: 2
思路:
代码:
public int add(int a, int b) {
int sum = a ^ b; //非进位
int carry = (a & b) << 1; //进位
return carry == 0 ? sum : add(sum, carry);
}