目录
入门
简单
1-替换字符串里的空格
思路:依次遍历String中的每一个字符string.charAt(i),用StringBuilder来存储最终结果,判断是否为空格,是则往stringbuilder添加要替换成的字符,否则直接添加。
2-返回数组中重复的数字
思路:用HashSet来存储数据,遍历数组将数字添加到set里,用set.contains(i)方法判断是否有重复,有则直接返回。
public int duplicate (int[] numbers) {
// write code here
Set<Integer> set = new HashSet<>();
for(int i : numbers){
if(!set.contains(i)){
set.add(i);
}else{
return i;
}
}
return -1;
}
3-从尾到头打印链表(反转链表)
思路1:递归,遍历到链表尾部,在将尾部数据添加到数组或集合里
思路2:栈
4-用两个栈实现队列
public class Solution {
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
public void push(int node) {
stack1.push(node);
}
public int pop() {
if(stack1.empty()&&stack2.empty()){
throw new RuntimeException("Queue is empty!");
}
if(stack2.empty()){
while(!stack1.empty()){
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
}
5- 打印从1到最大的n位数
思路:最大的n位数就是10的n次方减一,如1对应9,2对应99
public int[] printNumbers (int n) {
// write code here
int max =(int) Math.pow(10,n)-1;
int[] res = new int[max];
for(int i = 1;i<=max;i++){
res[i-1] = i;
}
return res;
}
6-删除链表的节点
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点
思路:迭代遍历
public class Solution {
public ListNode deleteNode (ListNode head, int val) {
//加入一个头节点
ListNode res = new ListNode(0);
res.next = head;
//前序节点
ListNode pre = res;
//当前节点
ListNode cur = head;
//遍历链表
while(cur != null){
//找到目标节点
if(cur.val == val){
//断开连接
pre.next = cur.next;
break;
}
pre = cur;
cur = cur.next;
}
//返回去掉头节点
return res.next;
}
}
7-链表中倒数第k个结点
思路:
- step 1:准备一个快指针,从链表头开始,在链表上先走kkk步。
- step 2:准备慢指针指向原始链表头,代表当前元素,则慢指针与快指针之间的距离一直都是kkk。
- step 3:快慢指针同步移动,当快指针到达链表尾部的时候,慢指针正好到了倒数kkk个元素的位置。
public class Solution {
public ListNode FindKthToTail (ListNode pHead, int k) {
int n = 0;
ListNode fast = pHead;
ListNode slow = pHead;
//快指针先行k步
for(int i = 0; i < k; i++){
if(fast != null)
fast = fast.next;
//达不到k步说明链表过短,没有倒数k
else
return slow = null;
}
//快慢指针同步,快指针先到底,慢指针指向倒数第k个
while(fast != null){
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
8-反转链表
思路1:使用栈解决
import java.util.Stack;
public class Solution {
public ListNode ReverseList(ListNode head) {
Stack<ListNode> stack= new Stack<>();
//把链表节点全部摘掉放到栈中
while (head != null) {
stack.push(head);
head = head.next;
}
if (stack.isEmpty())
return null;
ListNode node = stack.pop();
ListNode dummy = node;
//栈中的结点全部出栈,然后重新连成一个新的链表
while (!stack.isEmpty()) {
ListNode tempNode = stack.pop();
node.next = tempNode;
node = node.next;
}
//最后一个结点就是反转前的头结点,一定要让他的next
//等于空,否则会构成环
node.next = null;
return dummy;
}
}
9-合并两个有序链表
思路1:递归
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1==null){
return list2;
}
else if(list2==null){
return list1;
}
if(list2.val>list1.val){
list1.next = Merge(list1.next,list2);
return list1;
}
else{
list2.next = Merge(list1,list2.next);
return list2;
}
}
}
思路2:双指针
10-二叉树的镜像
思路:递归。
因为我们需要将二叉树镜像,意味着每个左右子树都会交换位置,如果我们从上到下对遍历到的节点交换位置,但是它们后面的节点无法跟着他们一起被交换,因此我们可以考虑自底向上对每两个相对位置的节点交换位置,这样往上各个子树也会被交换位置。
自底向上的遍历方式,我们可以采用后序递归的方法。
public class Solution {
public TreeNode Mirror (TreeNode pRoot) {
//空树返回
if(pRoot == null)
return null;
//先递归子树
TreeNode left = Mirror(pRoot.left);
TreeNode right = Mirror(pRoot.right);
//交换
pRoot.left = right;
pRoot.right = left;
return pRoot;
}
}
11-对称的二叉树
给定一棵二叉树,判断其是否是自身的镜像(即:是否对称)
思路1:递归
public class Solution {
boolean recursion(TreeNode root1, TreeNode root2){
//可以两个都为空
if(root1 == null && root2 == null)
return true;
//只有一个为空或者节点值不同,必定不对称
if(root1 == null || root2 == null || root1.val != root2.val)
return false;
//每层对应的节点进入递归比较
return recursion(root1.left, root2.right) && recursion(root1.right, root2.left);
}
boolean isSymmetrical(TreeNode pRoot) {
return recursion(pRoot, pRoot);
}
}
思路2:队列
public class Solution {
boolean isSymmetrical(TreeNode pRoot) {
//空树为对称的
if(pRoot == null)
return true;
//辅助队列用于从两边层次遍历
Queue<TreeNode> q1 = new LinkedList<TreeNode>();
Queue<TreeNode> q2 = new LinkedList<TreeNode>();
q1.offer(pRoot.left);
q2.offer(pRoot.right);
while(!q1.isEmpty() && !q2.isEmpty()){
//分别从左边和右边弹出节点
TreeNode left = q1.poll();
TreeNode right = q2.poll();
//都为空暂时对称
if(left == null && right == null)
continue;
//某一个为空或者数字不相等则不对称
if(left == null || right == null || left.val != right.val)
return false;
//从左往右加入队列
q1.offer(left.left);
q1.offer(left.right);
//从右往左加入队列
q2.offer(right.right);
q2.offer(right.left);
}
//都检验完都是对称的
return true;
}
}
12-顺时针打印矩阵
思路:边界模拟法
- step 1:首先排除特殊情况,即矩阵为空的情况。
- step 2:设置矩阵的四个边界值,开始准备螺旋遍历矩阵,遍历的截止点是左右边界或者上下边界重合。
- step 3:首先对最上面一排从左到右进行遍历输出,到达最右边后第一排就输出完了,上边界相应就往下一行,要判断上下边界是否相遇相交。
- step 4:然后输出到了右边,正好就对最右边一列从上到下输出,到底后最右边一列已经输出完了,右边界就相应往左一列,要判断左右边界是否相遇相交。
- step 5:然后对最下面一排从右到左进行遍历输出,到达最左边后最下一排就输出完了,下边界相应就往上一行,要判断上下边界是否相遇相交。
- step 6:然后输出到了左边,正好就对最左边一列从下到上输出,到顶后最左边一列已经输出完了,左边界就相应往右一列,要判断左右边界是否相遇相交。
- step 7:重复上述3-6步骤直到循环结束。
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> printMatrix(int [][] matrix) {
ArrayList<Integer> res = new ArrayList<>();
//先排除特殊情况
if(matrix.length == 0) {
return res;
}
//左边界
int left = 0;
//右边界
int right = matrix[0].length - 1;
//上边界
int up = 0;
//下边界
int down = matrix.length - 1;
//直到边界重合
while(left <= right && up <= down){
//上边界的从左到右
for(int i = left; i <= right; i++)
res.add(matrix[up][i]);
//上边界向下
up++;
if(up > down)
break;
//右边界的从上到下
for(int i = up; i <= down; i++)
res.add(matrix[i][right]);
//右边界向左
right--;
if(left > right)
break;
//下边界的从右到左
for(int i = right; i >= left; i--)
res.add(matrix[down][i]);
//下边界向上
down--;
if(up > down)
break;
//左边界的从下到上
for(int i = down; i >= up; i--)
res.add(matrix[i][left]);
//左边界向右
left++;
if(left > right)
break;
}
return res;
}
}
13- 包含min函数的栈
思路:双栈法。一个用来存原数据,一个用来存最小值
import java.util.Stack;
public class Solution {
//用于栈的push 与 pop
Stack<Integer> s1 = new Stack<Integer>();
//用于存储最小min
Stack<Integer> s2 = new Stack<Integer>();
public void push(int node) {
s1.push(node);
//空或者新元素较小,则入栈
if(s2.isEmpty() || s2.peek() > node)
s2.push(node);
else
//重复加入栈顶
s2.push(s2.peek());
}
public void pop() {
s1.pop();
s2.pop();
}
public int top() {
return s1.peek();
}
public int min() {
return s2.peek();
}
}
14-从上往下打印二叉树
思路:层次遍历,队列
import java.util.*;
public class Solution {
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
ArrayList<Integer> res = new ArrayList();
if(root == null)
//如果是空,则直接返回空数组
return res;
//队列存储,进行层次遍历
Queue<TreeNode> q = new ArrayDeque<TreeNode>();
q.offer(root);
while(!q.isEmpty()){
TreeNode cur = q.poll();
res.add(cur.val);
//若是左右孩子存在,则存入左右孩子作为下一个层次
if(cur.left != null)
q.add(cur.left);
if(cur.right != null)
q.add(cur.right);
}
return res;
}
}
15-数组中出现次数超过一半的数字
思路:哈希法
import java.util.*;
public class Solution {
public int MoreThanHalfNum_Solution(int [] array) {
//哈希表统计每个数字出现的次数
HashMap<Integer, Integer> mp = new HashMap<Integer, Integer>();
//遍历数组
for(int i = 0; i < array.length; i++){
//统计频率
if(!mp.containsKey(array[i]))
mp.put(array[i], 1);
else
mp.put(array[i], mp.get(array[i]) + 1);
//一旦有个数大于长度一半的情况即可返回
if(mp.get(array[i]) > array.length / 2)
return array[i];
}
return 0;
}
}
16-连续子数组的最大和
方法1:暴力法,时间复杂度O(n^2),空间复杂度O(1)
public int FindGreatestSumOfSubArray(int[] array) {
int max = array[0];
int sum = 0;
for(int i=0;i<array.length;i++){
// 每开启新的循环,需要把sum归零
sum = 0;
for(int j=i;j<array.length;j++){
// 这里是求从i到j的数值和
sum += array[j];
// 每次比较,保存出现的最大值
max = Math.max(max,sum);
}
}
return max;
}
方法2:动态规划,时间复杂度O(n),空间复杂度O(n)
public int FindGreatestSumOfSubArray(int[] array) {
int[] dp = new int[array.length];
int max = array[0];
dp[0] = array[0];
for(int i=1;i<array.length;i++){
// 动态规划,状态转移方程,确定dp[i]的最大值
dp[i] = Math.max(dp[i-1] + array[i], array[i]);
// 每次比较,保存出现的最大值
max = Math.max(max,dp[i]);
}
return max;
}
方法3:优化动态规划,时间复杂度O(n),空间复杂度O(1)
public int FindGreatestSumOfSubArray(int[] array) {
int sum = 0;
int max = array[0];
for(int i=0;i<array.length;i++){
// 优化动态规划,确定sum的最大值
sum = Math.max(sum + array[i], array[i]);
// 每次比较,保存出现的最大值
max = Math.max(max,sum);
}
return max;
}
17-第一个只出现一次的字符
思路:哈希表
import java.util.*;
public class Solution {
public int FirstNotRepeatingChar(String str) {
HashMap<Character, Integer> mp = new HashMap<>();
//统计每个字符出现的次数
for(int i = 0; i < str.length(); i++)
mp.put(str.charAt(i), mp.getOrDefault(str.charAt(i), 0) + 1);
//找到第一个只出现一次的字母
for(int i = 0; i < str.length(); i++)
if(mp.get(str.charAt(i)) == 1)
return i;
//没有找到
return -1;
}
}
18-数字序列中某一位的数字
思路:
- step 1:通过对每个区间起点数字的计算,按照上述规律求得该区间的位数,n不断减去它前面区间的位数,定位到属于它的区间。
- step 2:通过除以位数定位n在哪个数字上,用字符串形式表示。
- step 3:通过在字符串上位置对几位数取模定位目标数字。
import java.util.*;
public class Solution {
public int findNthDigit (int n) {
//记录n是几位数
int digit = 1;
//记录当前位数区间的起始数字:1,10,100...
long start = 1;
//记录当前区间之前总共有多少位数字
long sum = 9;
//将n定位在某个位数的区间中
while(n > sum){
n -= sum;
start *= 10;
digit++;
//该区间的总共位数
sum = 9 * start * digit;
}
//定位n在哪个数字上
String num = "" + (start + (n - 1) / digit);
//定位n在数字的哪一位上
int index = (n - 1) % digit;
return (int)(num.charAt(index)) - (int)('0');
}
}
19-两个链表的第一个公共结点
思路1:两层枚举,逐个检查哪个节点相同
public class Solution {
public ListNode FindFirstCommonNode(ListNode a, ListNode b) {
for (ListNode h1 = a; h1 != null ; h1 = h1.next) {
for (ListNode h2 = b; h2 != null ; h2 = h2.next) {
if (h1 == h2) return h1;
}
}
return null;
}
}
思路二:
使用 Set
数据结构,先对某一条链表进行遍历,同时记录下来所有的节点。
然后在对第二链条进行遍历时,检查当前节点是否在 Set
中出现过,第一个在 Set
出现过的节点即是交点。
import java.util.*;
public class Solution {
public ListNode FindFirstCommonNode(ListNode a, ListNode b) {
Set<ListNode> set = new HashSet<>();
while (a != null) {
set.add(a);
a = a.next;
}
while (b != null && !set.contains(b)) b = b.next;
return b;
}
}
思路三:遍历两个链表,找出较长的那个链表,忽略较长链表的头部多出的一截(多出的一截不可能存在公共节点)然后依次对比节点是否相等,返回第一个节点相等的指针即可。空间复杂度O(1)事件复杂度O(m+n)(两个链表都进行了遍历,找出长度)
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
int len1 = 0;
int len2 = 0;
ListNode p1 = pHead1;
ListNode p2 = pHead2;
while(p1!=null){len1++;p1=p1.next;}
while(p2!=null){len2++;p2=p2.next;}
ListNode maxlist = len1>len2?pHead1:pHead2;
ListNode minlist = len1>len2?pHead2:pHead1;
int count =0;
while(maxlist!=minlist){
count++;
if(count>Math.abs(len1-len2)){
minlist = minlist.next;
}
maxlist = maxlist.next;
}
return maxlist;
}
}
20-数字在升序数组中出现的次数
思路一:暴力
import java.util.*;
public class Solution {
public int GetNumberOfK(int [] array , int k) {
int n = 0;
for(int i = 0;i<array.length;i++){
if(array[i]==k)
n++;
if(array[i]>k)
break;
}
return n;
}
}
思路2:二分
- step 1:写一个二分查找的函数在数组中找到某个元素出现的位置。每次检查区间中点值,根据与中点的大小比较,确定下一次的区间。
- step 2:分别使用二分查找,找到k+0.5和k-0.5应该出现的位置,中间的部分就全是k,相减计算次数就可以了。
public class Solution {
//二分查找
private int bisearch(int[] data, double k){
int left = 0;
int right = data.length - 1;
//二分左右界
while(left <= right){
int mid = (left + right) / 2;
if(data[mid] < k)
left = mid + 1;
else if(data[mid] > k)
right = mid - 1;
}
return left;
}
public int GetNumberOfK(int [] array , int k) {
//分别查找k+0.5和k-0.5应该出现的位置,中间的部分就全是k
return bisearch(array, k + 0.5) - bisearch(array, k - 0.5);
}
}
21-二叉树的深度
思路1:递归
import java.lang.Math;
public class Solution {
public int TreeDepth(TreeNode pRoot)
{
if(pRoot == null){
return 0;
}
int left = TreeDepth(pRoot.left);
int right = TreeDepth(pRoot.right);
return Math.max(left, right) + 1;
}
}
思路2:层次遍历
22-扑克牌顺子
思路:将 nums 数组依次装入 set集合,遇到 0 则返回装下一个元素,出现重复元素则返回 false,并在其中记录max,min,最终max-min >= 5的都不是顺子;
import java.util.*;
public class Solution {
public boolean IsContinuous(int [] numbers) {
Set<Integer> set = new HashSet<>();
int max = Integer.MIN_VALUE, min =Integer.MAX_VALUE;
//遍历数组
for (int number:
numbers) {
if(number == 0) {
continue;
}
//包含相同牌则直接返回,否则加入
if(set.contains(number)){
return false;
}else {
set.add(number);
}
//每次遍历记录最大值,最小值
max = StrictMath.max(max,number);
min = StrictMath.min(min,number);
}
return max - min < 5;
}
}
import java.util.*;
public class Solution {
public boolean IsContinuous(int [] numbers) {
int king = 0,max,min;
//将数组排序
Arrays.sort(numbers);
for(int i = 0; i < 4; i++) {
//记录王牌个数
if(numbers[i] == 0) king++;
else if(numbers[i] == numbers[i + 1]) return false;
}
max = numbers[4];
min = numbers[king];// king的个数会占去前置位的数组,nums[king]必然是最小值
//最大值和最小值进行比较小于5即是顺子
return numbers[4] - numbers[king] < 5;
}
}
23-