文章目录
-
- 3、数组中重复的数字
- 4、二维数组中的查找
- 5、替换空格
- 6、从尾到头打印链表
- 7、重建二叉树
- 9、两个栈来实现一个队列
- 10-1、斐波那契数列
- 10-2、跳台阶
- 11、旋转数组的最小数字
- 12、矩阵中的路径
- 13、机器人的运动范围
- 14-1、剪绳子
- 14-2、剪绳子2
- 15、二进制中1的个数
- 16、数值的整数次方
- 17、打印从1到最大的n位数
- 18、删除链表的节点
- 19、正则表达式匹配
- 20、表示数值的字符串
- 21、调整数组顺序使奇数位于偶数前面
- 22、链表中倒数第k个结点
- 24、反转链表
- 25、合并两个排序的链表
- 26、树的子结构
- 27、二叉树的镜像
- 28、对称的二叉树
- 29、顺时针打印矩阵
- 30、包含min函数的栈
- 31、栈的压入、弹出序列
- 32-1、从上往下打印二叉树
- 32-2、从上往下打印二叉树
- 32-3、从上往下打印二叉树
- 33、二叉搜索树的后序遍历序列
- 34、二叉树中和为某一值的路径
- 35、复杂链表的复制
- 36、二叉搜索树和双向链表
- 37、序列化二叉树
- 38、字符串的排列
- 39、数组中出现次数超过一半的数字
- 40、最小的K个数
- 41、数据流中的中位数
- 42、连续子数组的最大和
- 43、数字序列中某一位的数字
- 44、数字序列中某一位的数字
- 45、把数组排成最小的数
- 46、连续子数组的最大和
- 47、礼物的最大价值
- 48、最长不含重复字符的子字符串
- 49、丑数
- 50、第一个只出现一次的字符
- 51、数组中的逆序对
- 52、两个链表的第一个公共节点
- 53-1、在排序数组中查找数据i
- 54、二叉搜索树的第k大的节点
- 55-1、二叉树的深度
- 55-2、平衡二叉树
- 56-1、数组中数字出现的次数
- 56-2、数组中数字出现的次数
- 57-1、和为s的两个数字
- 57-2、和为s的两个数字
- 58-1、翻转单词顺序
- 58-2、左旋转字符串
- 59-1、滑动窗口的最大值
- 59-2、队列的最大值
- 60、n个骰子的点数
- 61、扑克牌中的顺子
- 62、圆圈中剩下的数字
- 63、股票的最大利润
- 64、求1+2+...+n
- 65、不用加减乘除做加法
- 66、构建乘积数组
- 67、把字符串转换成整数
- 68-1、二叉搜索树的最近公共祖先
- 68-2、二叉树的最近公共祖先
3、数组中重复的数字
题目描述
找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:
2或3
思路分析
思路一:题目说要寻找一个重复的元素,因此可以考虑使用HashSet来解决该问题。集合中不能存在重复的元素,将数组中每个元素都添加到Set中,如果添加失败,则代表该元素是重复元素。使用这种方法相当于以空间换时间,时间复杂度会最小。
思路二:原地交换法,题目隐藏了条件就是所有数字都在 0~n-1 的范围内,因此我们可以考虑使用原地交换的方法。遍历数组,如果数组索引等于对应的元素,则跳过,否则;则进行交换,即把数值为n的元素放在数组的第n个索引,当发现第n个索引已经有相同的元素时,则返回重复的元素。
代码
思路一:
class Solution {
public int findRepeatNumber(int[] nums) {
Set<Integer> set = new HashSet<>();
int result = -1;
for(int num:nums){
if(!set.add(num)){
result=num;
}
}
return result;
}
}
思路二:
class Solution {
public int findRepeatNumber(int[] nums) {
for(int i=0; i<nums.length; i++){
if(i == nums[i]){
continue;
}else{
if(nums[i] == nums[nums[i]]) return nums[i];
int temp = nums[i];
nums[i]=nums[nums[i]];
nums[temp]=temp;
}
}
return -1;
}
}
4、二维数组中的查找
题目描述
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
示例
输入:
[[1,2,8,9],[2,4,9,12],[4,7,10,13],[6,8,11,15]],7
输出:
true
思路分析
因为这个一个排好序的二维数组,查找时考虑使用二分查找。然而由题目可知,这个二维数组的排序有一定规律,按照二分查找的思路就是选定一个mid点,然而进行查找。因此考虑选择二维数组右上方的元素作为mid,如果target等于mid,则找到;如果target大于mid,就说明target不在第一行,此时接着判断第二行和最后一列,循环即可;如歌targte小于mid,就说明target不在最后一列,此时接着判断第一行和倒数第二列,循环即可。
代码
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
if(matrix.length == 0 || matrix[0].length == 0) return false;
int i = 0;
int j = matrix[0].length-1;
boolean flag = false;
while(i<=matrix.length-1 && j>=0){
if(target == matrix[i][j]){
flag=true;
break;
}
else if(target > matrix[i][j]){
i++;
}
else{
j--;
}
}
return flag;
}
}
5、替换空格
题目描述
请实现一个函数,将一个字符串中的每个空格替换成“%20”。
示例
输入:
"We Are Happy"
输出:
"We%20Are%20Happy"
思路分析
题目需要替换字符串,因此考虑使用StringBuffer类来实现。新建一个StringBuffer,遍历原字符串,如果字符为空格,则在sb中添加一个%20,如果字符不为空格,则添加该字符。
代码
public String replaceSpace (String s) {
StringBuffer sb = new StringBuffer();
for(int i=0; i<s.length(); i++){
if(s.charAt(i)==' '){
sb.append("%20");
}
else{
sb.append(s.charAt(i));
}
}
return sb.toString();
}
6、从尾到头打印链表
题目描述
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
示例
输入:
head = [1,3,2]
输出:
[2,3,1]
思路分析
思路一:从尾到头打印这个链表,会考虑到使用先进后出的栈来解决。
思路二:另外一个思路也可以首先遍历一遍链表,记录下其长度,然后建立一个相同长度的数组,从后往前赋值即可。
代码
思路一:
class Solution {
public int[] reversePrint(ListNode head) {
Stack<ListNode> stack = new Stack<ListNode>();
for(ListNode node=head; node!=null; node=node.next){
stack.push(node);
}
int size = stack.size(); //记录栈长度,否则随着出栈栈长度会变小
int[] result = new int[size];
for(int i=0; i<size; i++){
result[i]=stack.pop().val;
}
return result;
}
}
思路二:
class Solution {
public int[] reversePrint(ListNode head) {
ListNode tmp = head;
int len = 0;
while(tmp!=null){
tmp=tmp.next;
len++;
}
int[] array = new int[len];
for(int j=len-1; j>=0; j--){
array[j] = head.val;
head=head.next;
}
return array;
}
}
7、重建二叉树
题目描述
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
示例
输入:
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
输出:
3
/ \
9 20
/ \
15 7
思路分析
思路一:知道二叉树的前序遍历和中序遍历,可以考虑使用递归来解决。首先通过前序遍历得到该二叉树的root结点,示例中root结点为1。知道root结点后,通过中序遍历知道了root的左子树的前序遍历为[9],中序遍历为[9],右子树前序遍历为[20,15,7],[15,20,7],接着分别对root的左子树和右子树进行同样的递归操作即可。
思路二:整个思路跟思路一类似,不同的是为了提高效率,加入HashMap存储中序遍历的值与索引的映射
代码
思路一:
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(preorder.length==0 | inorder.length==0){
return null;
}
TreeNode root = new TreeNode(preorder[0]);
for(int i=0; i<inorder.length; i++){
//从前序遍历中找到中序遍历中的二叉树的根结点
if(inorder[i] == preorder[0]){
root.left=buildTree(Arrays.copyOfRange(preorder,1,i+1),Arrays.copyOfRange(inorder,0,i));
root.right=buildTree(Arrays.copyOfRange(preorder,i+1,preorder.length),Arrays.copyOfRange(inorder,i+1,inorder.length));
break;
}
}
return root;
}
}
思路二:
class Solution {
int[] preorder;
HashMap<Integer,Integer> map = new HashMap<Integer, Integer>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
this.preorder = preorder;
for(int i=0; i<inorder.length; i++){
map.put(inorder[i], i);
}
return recur(0,0,preorder.length-1);
}
TreeNode recur(int root, int left, int right) {
//递归终止条件
if(left>right) return null;
//建立新结点
TreeNode node = new TreeNode(preorder[root]);
//查找出preorder[root]在inorder中的索引
int i = map.get(preorder[root]);
//左子树:跟结点为root+1,范围为preorder[left]-preorder[i-1]
node.left=recur(root+1,left,i-1);
//右子树:跟结点i-left+root+1(根结点索引+左子树长度+1),范围为preorder[i+1]-preorder[right]
node.right=recur(i-left+root+1,i+1,right);
return node;
}
}
9、两个栈来实现一个队列
题目描述
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
思路分析
考虑使用两个栈来实现一个队列,首先考虑的是该队列只进不出,这样就可以直接使用stack1来存储该“队列”的所有元素。一旦考虑到出列的情况,就相当于要把stack1的所有元素先倒置,即把stack1的元素全部装进stack2,这样stack2中的元素就是stack1中所有元素的倒置,这样出列操作就可以通过stack2的pop方法来完成。还有一些细节就是因为队列是先进先出,因为当需要出列的时候应该先检查stack2是否为空,如果为空就将stack1的元素放入,如果不为空则直接出列即可。
代码
class CQueue {
Stack<Integer> stack1;
Stack<Integer> stack2;
public CQueue() {
stack1 = new Stack<Integer>();
stack2 = new Stack<Integer>();
}
public void appendTail(int value) {
stack1.push(value);
}
public int deleteHead() {
if(stack2.isEmpty()){
while(!stack1.isEmpty()){
stack2.push(stack1.pop());
}
}
if(stack2.isEmpty()){
return -1;
}else{
return stack2.pop();
}
}
}
10-1、斐波那契数列
题目描述
写一个函数,输入 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。
示例
输入:
4
输出:
3
思路分析
这题最简单的思路便是递归,但是递归会产生许多的重复计算,因此可以考虑使用动态规划来优化。动态规划的状态转移方程便为斐波那契的通项公式dp[n]=dp[n-1]+dp[n-2]
代码
class Solution {
public int fib(int n) {
if(n<=1){
return n;
}
int a=0;
int b=1;
int sum;
for(int i=0; i<n; i++){
sum=(a+b)%1000000007;
a=b;
b=sum;
}
return a;
}
}
10-2、跳台阶
题目描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
思路分析
这道题跟前面的斐波那契数列很类似。我们可以用反向思维来理解,假设青蛙跳到第n阶台阶,那么它的上一步会在哪呢?要么在n-1阶台阶,要么在n-2阶台阶,这样我们就可以得到青蛙跳到第n阶台阶的跳法等于青蛙跳到第n-1阶台阶的跳法与跳到第n-2阶台阶的跳法之和。同样使用动态规划求解。
代码
class Solution {
public int numWays(int n) {
int a=1;
int b=1;
int sum;
for(int i=0; i<n; i++){
sum=(a+b)%1000000007;
a=b;
b=sum;
}
return a;
}
}
11、旋转数组的最小数字
题目描述
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
示例
输入:
[3,4,5,1,2]
输出:
1
思路分析
这题最开始没有思路,网上比较流行的就是使用二分搜索来完成,但是实现的过程也有一些细微的差异,下面列了一个我觉得较为理想的答案。首先mid大于high时,更新左边界值,而当mid小于high时,则更新右边界值,当两者相等时,很难判断,则让high自减1继续判断。
代码
class Solution {
public int minArray(int[] numbers) {
if(numbers.length == 0){
return 0;
}
int low = 0;
int high = numbers.length-1;
while(low<high){
int mid=(low+high)/2;
if(numbers[mid]>numbers[high]){
low=mid+1;
}else if(numbers[mid]==numbers[high]){
high=high-1;
}else{
high=mid;
}
}
return numbers[high];
}
}
12、矩阵中的路径
题目描述
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
示例
输入:
board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:
true
思路分析
这道题目用的思想是回溯算法,典型的就是深度优先遍历+剪枝。逐个单元格进行深度优先遍历,一旦判断存在即返回true,否则返回false。
代码
class Solution {
public boolean exist(char[][] board, String word) {
char[] array = word.toCharArray();
for(int i=0; i<board.length; i++){
for(int j=0; j<board[0].length; j++){
if(dfs(board,array,i,j,0)) return true;
}
}
return false;
}
public boolean dfs(char[][] board, char[] array, int i, int j, int k){
//上下左右的元素越界或者不等于array[k]时,返回false
if(i<0 || i>=board.length || j<0 || j>=board[0].length || board[i][j] != array[k]){
return false;
}
//当k==array.length-1时,还未返回false则返回true
if(k==array.length-1){
return true;
}
board[i][j]='\0';
boolean res = dfs(board, array, i+1, j, k+1) || dfs(board, array, i-1, j, k+1) || dfs(board, array, i, j+1, k+1) || dfs(board, array, i, j-1, k+1);
board[i][j]=array[k];
return res;
}
}
13、机器人的运动范围
题目描述
地上有一个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
思路分析
思路一:使用回溯算法,即深度优先算法,先定义一个计算数位和的函数,然后进行深度优先遍历。
思路二:使用广度优先算法,引入队列来辅助进行。
代码
思路一:
class Solution {
//计算数位和方法
public int sums(int k){
int s=0;
while(k!=0){
s+=k%10;
k=k/10;
}
return s;
}
//全局变量
int m,n,k;
boolean[][] visited;
public int movingCount(int m, int n, int k) {
this.m=m; this.n=n; this.k=k;
this.visited=new boolean[m][n];
return dfs(0,0,0,0);
}
public int dfs(int i, int j, int xi, int xj){
if(i>=m || j>=n || xi+xj>k || visited[i][j]) return 0;
visited[i][j]=true;
return 1+dfs(i+1,j,sums(i+1),sums(j))+dfs(i,j+1,sums(i),sums(j+1));
}
}
思路二:
class Solution {
//计算数位和方法
public int sums(int k){
int s=0;
while(k!=0){
s+=k%10;
k=k/10;
}
return s;
}
public int movingCount(int m, int n, int k) {
boolean[][] visited = new boolean[m][n];
Queue<int[]> queue = new LinkedList<int[]>();
int sum=0;
queue.add(new int[]{
0,0,0,0});
while(!queue.isEmpty()){
int[] x = queue.poll();
int i=x[0]; int j=x[1]; int xi=x[2]; int xj=x[3];
if(i>=m || j>=n || xi+xj>k || visited[i][j]) continue;
visited[i][j]=true;
sum++;
queue.add(new int[]{
i+1,j,sums(i+1),sums(j)});
queue.add(new int[]{
i,j+1,sums(i),sums(j+1)});
}
return sum;
}
}
14-1、剪绳子
题目描述
给你一根长度为 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
思路分析
思路一:使用动态规划算法,分析如下:首先dp[2]=1,然后当绳长i>=3时,我们可以假设剪掉一段长度为j的绳子,然后剩下的长度就是i-j,此时我们可以选择继续剪或者不剪。如果不剪的话乘积就为i×j,剪的话乘积就为i×dp[i-j],取最大者。然后2<=j<i开始循环
思路二:使用贪心算法,先计算出n=2,3,4的情况,我们可以看出当n=2,3时,max分别等于1,2;n=4时,max=2×2=4,当n>4时,可以对n-=3,此时乘积是最大的。
代码
思路一:
class Solution {
public int cuttingRope(int n) {
int[] dp = new int[n+1];
dp[2] = 1;
for(int i=3; i<n+1; i++){
for(int j=2; j<i; j++){
int temp = dp[i];
int max = Math.max(j*(i-j),j*dp[i-j]);
dp[i] = Math.max(temp,max);
}
}
return dp[n];
}
}
思路二:
class Solution {
public int cuttingRope(int n) {
if(n==2) return 1;
if(n==3) return 2;
int res=1;
while(n>4){
n-=3;
res*=3;
}
return res*n;
}
}
14-2、剪绳子2
题目描述
给你一根长度为 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
思路分析
这道题只能使用贪心算法,不能使用动态规划,因为结果需要取模返回不能使用max来找到上一步的最大乘积。
代码
class Solution {
public int cuttingRope(int n) {
if(n==2) return 1;
if(n==3) return 2;
long res=1; //注意使用long,用int会溢出
while(n>4){
res=res*3%1000000007;
n-=3;
}
return (int)(res * n % 1000000007);
}
}
15、二进制中1的个数
题目描述
输入一个整数,输出该数32位二进制表示中1的个数。其中负数用补码表示。
输入:
10
输出:
2
思路分析
本题运用的知识是按位与(&)运算和无符号右移(>>>),当一个数的二进制进行无符号右移n位后,与1进行&运算,当其结果等于1时,则代表这个数二进制的第n位为1,反之为0。依据这个方法可以计算出一个二进制数的1的个数。
代码
public class Solution {
public int NumberOf1(int n) {
int count = 0;
for(int i = 0; i < 32; i++){
if((n >>> i & 1) == 1)
count++;
}
return count;
}
}
16、数值的整数次方
题目描述
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
保证base和exponent不同时为0
输入:
2,3
输出:
8.00000
思路分析
思路一:无论使用哪种思路都需要考虑进行n的判断,当n小于0时,将x取分数并且n取反,当n等于0时,直接返回1。使用递归的快速幂求解,当我们要求一个数的8次方时,我们只需要将这个的4次方再平方即可,奇数次方则将其再乘多一个base即可,可构造递归。
思路二:仍然是使用快速幂求解,不同的是使用二进制的快速幂,用的是迭代而不是递归。
代码
class Solution {
public double q_power(double x, long b){
if(b ==0){
return 1;
}
double result = q_power(x,b/2);
if(b%2 == 1){
return result*result*x;
}else{
return result * result;
}
}
public double myPow(double x, int n) {
long b = n;
if(n<0){
x = 1 / x;
b = -b;
}
return q_power(x,b);
}
}
思路二:
class Solution {
public double myPow(double x, int n) {
if(n==0) return 1;
long b = n; //使用long存储n,防止溢出
double res = 1.0;
if(n<0){
x=1/x;
b=-b;
}
while(b>0){
if((b&1)==1) res*=x;
x*=x;
b>>=1;
}
return res;

最低0.47元/天 解锁文章
1415





