文章目录
128.最长连续序列——hashset暴力破解+重复优化
给定一个未排序的整数数组,找出最长连续序列的长度。要求算法的时间复杂度为 O(n)。
思路:
- 这道题很容易想到hashset,然后遍历每一个出现的数字来统计最长序列长度;
- 假如是数组 54367,当我们遇到 5 的时候计算一遍 567,遇到 4 又计算一遍 4567,遇到 3 又计算一遍 34567,这样会存在很多的重复计算。
- 明显从 3 开始才是我们想要的序列,只考虑从序列最小的数开始即可;
- 当考虑 n 的时候,我们先看一看 n - 1 是否存在,如果不存在,那么从 n 开始就是我们需要考虑的序列了。否则的话,直接跳过。
public class No128 {
public int longestConsecutive(int[] nums) {
HashSet<Integer> set = new HashSet<>();
for (int i : nums) {
set.add(i);
}
int max = 0;
for (int i = 0; i < nums.length; i++) {
int num = nums[i];
if (!set.contains(num - 1)) {
int count = 0;
while (set.contains(num)) {
count++;
num++;
}
max = Math.max(max, count);
}
}
return max;
}
}
958.二叉树的完全性检验——BFS+标志位
给定一个二叉树,确定它是否是一个完全二叉树。
思路:
- 设置一个标志位,判断是否出现null
- 如果出现null后都是null,返回true
- 如果出现null后,又出现节点,直接返回false
public class No958 {
public boolean isCompleteTree(TreeNode root) {
if(root == null) return false;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
boolean flag = false;
while (!queue.isEmpty()){
TreeNode cur = queue.poll();
if(cur == null){
flag = true;
continue;
}
if(flag){
return false;
}
queue.add(cur.left);
queue.add(cur.right);
}
return true;
}
}
62. 不同路径——dp+滚筒数组优化
一个机器人位于一个 m x n 网格的左上角,机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角,问总共有多少条不同的路径?
public int uniquePaths(int m, int n) {
int[] dp = new int[n];
Arrays.fill(dp,1);
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
dp[j] += dp[j - 1];
}
}
return dp[n - 1];
}
162.寻找峰值——未排序数组的二分法
题目要求时间复杂度到 log N,所以要从二分法考虑,能用二分法是因为我们可以根据某个条件,直接抛弃一半的元素。题目告诉我们可以返回数组中的任意一个峰顶。所以我们只要确定某一半至少存在一个峰顶,那么另一半就可以抛弃掉。
public class No162 {
//线性
public int findPeakElement2(int[] nums) {
for (int i = 0; i < nums.length - 1; i++) {
if(nums[i] > nums[i + 1]){
return i;
}
}
return nums.length - 1;
}
//对数
public int findPeakElement(int[] nums) {
int left = 0;
int right = nums.length - 1;
while (left != right){
int mid = (left + right) >>> 1;
if(nums[mid] < nums[mid + 1]){
left = mid + 1;
}else{
right = mid;
}
}
return left;
}
}
322.零钱兑换——dp
- dp[i]表示零钱和为 i 的时候需要dp[i]个硬币才能凑出
- 如果没有任何一种硬币组合能组成总金额,返回 -1,我们可以将dp直接随便填充一个大于amout的数(不能是Integer.MAX_VALUE,因为状态转移那一步会越界)
- base case : dp[0] = 0,因为零钱和为0时没有一种方法可以凑出
public class No322 {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];//dp[i]表示amout为i的时候需要dp[i]个硬币才能凑出
Arrays.fill(dp,amount + 10);
dp[0] = 0;
for (int i = 1; i <= amount; i++) {
for(int coin : coins){
if(i - coin < 0){
continue;
}
dp[i] = Math.min(dp[i],1 + dp[i - coin]);
}
}
return (dp[amount] == amount + 10) ? -1 : dp[amount];
}
}
23.合并k个升序链表——归并排序,优先队列
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
归并排序
和数组的归并排序是一样的,不断递归到底层,然后两两合并,最终时间复杂度是 O(nlog(n))
public class No23 {
public ListNode merge(ListNode[] lists, int lo, int hi) {
if (lo == hi) {
return lists[lo];
}
int mid = (lo + hi) >>> 1;
ListNode l1 = merge(lists, lo, mid);
ListNode l2 = merge(lists, mid + 1, hi);
return mergeK2Lists(l1, l2);
}
public ListNode mergeK2Lists(ListNode l1, ListNode l2) {
ListNode prehead = new ListNode(-1);//哨兵
ListNode pre = prehead;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
pre.next = l1;
l1 = l1.next;
} else {
pre.next = l2;
l2 = l2.next;
}
pre = pre.next;
}
pre.next = l1 == null ? l2 : l1;
return prehead.next;
}
}
优先队列
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;
public class No23 {
public ListNode mergeKLists(ListNode[] lists) {
//定义比较器
Comparator<ListNode> cmp = new Comparator<ListNode>() {
@Override
public int compare(ListNode o1, ListNode o2) {
return o1.val - o2.val;
}
};
Queue<ListNode> queue = new PriorityQueue<>(cmp);
//Queue<ListNode> queue = new PriorityQueue<>((x,y)->x.val - y.val);
for (ListNode node : lists) {
if (node != null) {
queue.offer(node);
}
}
ListNode dummy = new ListNode(-1);
ListNode cur = dummy;
while (!queue.isEmpty()) {
cur.next = queue.poll();
cur = cur.next;
if (cur.next != null) {
queue.offer(cur.next);
}
}
return dummy.next;
}
}
41.寻找第一个缺失的正数——原地哈希
给你一个未排序的整数数组,请你找出其中没有出现的最小的正整数。
- 和剑指offer第三题挺像的,思路都是一样,原地哈希
- 在遍历数组的时候,如果当前位置的数字是 x,如果 x 在 [1,len] 范围内,则将 x 交换到指定位置也就是num[x - 1]上,用一个 while 循环再次判断新换来的数字是不是在 [1,len] 范围内
- 如果某位置的数字不在[1,len]内,或者在交换后的现在位置数字不在[1,len]内,直接忽略,遍历下一个就好
- 最后遍历一遍数组,第一个不满足 nums[i] != i + 1 的 i 就是我们要求的下标
public class No41 {
public int firstMissingPositive(int[] nums) {
int len = nums.length;
for (int i = 0; i < len; i++) {
//如果已经在正确的位置上了,就不需要改
if(nums[i] == i + 1){
continue;
}
//将[1,len]范围内的数字交换到指定位置上,如果某位置的数字不在[1,len]内,或者在交换后的现在位置数字不在[1,len]内,直接忽略
while (nums[i] > 0 && nums[i] <= len && nums[nums[i] - 1] != nums[i]) {
//满足在指定范围内、并且没有放在正确的位置上,才交换
swap(nums, nums[i] - 1, i);
}
}
// [1, -1, 3, 4]
for (int i = 0; i < len; i++) {
if (nums[i] != i + 1) {
return i + 1;
}
}
// 都正确则返回数组长度 + 1
return len + 1;
}
private void swap(int[] nums, int x, int y) {
int temp = nums[x];
nums[x] = nums[y];
nums[y] = temp;
}
}
101.对称二叉树——递归
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root==null) return true;
return recur(root.left,root.right);
}
public boolean recur(TreeNode left,TreeNode right){
if(left==null&&right==null) return true;
if(left==null||right==null) return false;
if(left.val!=right.val) return false;
return recur(left.left,right.right)&&recur(left.right,right.left);
}
}
200.岛屿数量——DFS,灵活使用标记
给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
- 每碰到一个 1 ,就把相应的连在一起的 1 改成 2,并把岛屿数量加 1;
class Solution {
public int numIslands(char[][] grid) {
if(grid.length==0||grid[0].length==0) return 0;
int count=0;//岛屿数量
for(int row=0;row<grid.length;row++){
for(int col=0;col<grid[0].length;col++){
if(grid[row][col]=='1'){
count++;
marked(grid,row,col,grid.length,grid[0].length);
}
}
}
return count;
}
//把岛屿1都改成2,统计个数
public void marked(char[][] grid,int i,int j,int rows,int cols){
//base case
if(i<0||j<0||i>rows-1||j>cols-1||grid[i][j]!='1'){
return;
}
grid[i][j]='2';//标记这个点已经来过
marked(grid,i-1,j,rows,cols);
marked(grid,i+1,j,rows,cols);
marked(grid,i,j+1,rows,cols);
marked(grid,i,j-1,rows,cols);
}
}
155.最小栈——数据栈+单调栈
class MinStack {
Stack<Integer> stack1;//数据栈
Stack<Integer> stack2;//辅助栈
/** initialize your data structure here. */
public MinStack() {
stack1 = new Stack<>();
stack2 = new Stack<>();
}
public void push(int x) {
stack1.add(x);
if(stack2.isEmpty() || stack2.peek()>=x){
stack2.add(x);
}
}
public int pop() {
if(stack1.isEmpty()){
throw new RuntimeException("空栈,操作非法");
}else{
int pop = stack1.pop();
if(pop == stack2.peek()){
stack2.pop();
}
return pop;
}
}
public int top() {
if(!stack1.isEmpty()){
return stack1.peek();
}
throw new RuntimeException("空栈,操作非法");
}
public int getMin() {
if(!stack2.isEmpty()){
return stack2.peek();
}
throw new RuntimeException("空栈,操作非法");
}
}