Java版 剑指offer笔记(一)

news2024/10/7 18:28:36

1.数组中重复的数字

思路1:

使用哈希表,哈希表是一种根据关键码(key)直接访问值(value)的一种数据结构。而这种直接访问意味着只要知道key就能在O(1)时间内得到value,因此哈希表常用来统计频率、快速检验某个元素是否出现过等。
step 1:遍历数组,将没有出现过的元素加入哈希表。
step 2:遇到的元素在哈希表中出现过就是重复数组。
step 3:遍历结束也没找到就返回-1.

import java.util.*;
public class Solution {
    public int duplicate (int[] numbers) {
        //哈希表记录重复
        HashMap<Integer, Integer> mp = new HashMap<>();
        //遍历数组
        for(int i = 0; i < numbers.length; i++){
            //如果没有出现过就加入哈希表
            if(!mp.containsKey(numbers[i]))
                mp.put(numbers[i], 1);
            //否则就是重复数字
            else
                return numbers[i];
        }
        //没有重复
        return -1;
    }
}

复杂度

时间:O(n),其中n为数组长度,遍历一次数组,哈希表每次操作都是O(1)
空间:O(n),哈希表最大的空间为数组长度

思路2(#):

既然数组长度为nnn只包含了0到n−1n-1n−1的数字,那么如果数字没有重复,这些数字排序后将会与其下标一一对应。那我们就可以考虑遍历数组,每次检查数字与下标是不是一致的,一致的说明它在属于它的位置上,不一致我们就将其交换到该数字作为下标的位置上,如果交换过程中,那个位置已经出现了等于它下标的数字,那肯定就重复了。

2.二维数组中的查找

思路1(暴力):直接遍历数组寻找

public class Solution {
    public boolean Find(int target, int [][] array) {
        //逐行逐列遍历
        for(int i=0;i<array.length;i++){
            for(int j=0;j<array[0].length;j++){
                if (array[i][j]==target){
                    return true;
                }
            }
        }
        return false;
    }
}

复杂度

时间:O(n^2) 空间:O(1)

思路2:循环一次,从左下角开始查找。

对于左下角的值 m,m 是该行最小的数,是该列最大的数。
step 1:首先获取矩阵的两个边长,判断特殊情况。
step 2:首先以左下角为起点,若是它小于目标元素,则往右移动去找大的,若是他大于目标元素,则往上移动去找小的。
step 3:若是移动到了矩阵边界也没找到,说明矩阵中不存在目标值。
用某行最小或某列最大与 target 比较,每次可剔除一整行或一整列。

public class Solution {
    public boolean Find(int target, int [][] array) {
        int rows = array.length;
        if(rows == 0){
            return false;
        }
        int cols = array[0].length;
        if(cols == 0){
            return false;
        }
        // 左下
        int row = rows-1;
        int col = 0;
        while(row>=0 && col<cols){
            if(array[row][col] < target){
                col++;
            }else if(array[row][col] > target){
                row--;
            }else{
                return true;
            }
        }
        return false;
    }
}

3.替换空格

思路1:

将字符串遍历成一个个字符,存储到StringBuilder中。如果遇到空格则替换成%20。

public String replaceSpace(String s) {
    StringBuilder stringBuilder = new StringBuilder();
    for (int i = 0; i < s.length(); i++) {
        if (s.charAt(i) == ' ')
            stringBuilder.append("%20");
        else
            stringBuilder.append(s.charAt(i));
    }
    return stringBuilder.toString();
}

复杂度

时间和空间复杂度都是O(n)

思路2:

将字符串存到字符数组里面(char[]),然后依次遍历字符数组中的值,遇到空格则添加三个字符。

public String replaceSpace(String s) {
    int length = s.length();
    char[] array = new char[length * 3];
    int index = 0;
    for (int i = 0; i < length; i++) {
        char c = s.charAt(i);
        if (c == ' ') {
            array[index++] = '%';
            array[index++] = '2';
            array[index++] = '0';
        } else {
            array[index++] = c;
        }
    }
    String newStr = new String(array, 0, index);
    return newStr;
}

复杂度

时间和空间复杂度都是O(n)

4.从尾到头打印链表

思路1:递归

新建一个list,在递归里面,判断链表的next是否为空,不为空把值放进list(因为是递归,所以add的时候会将链表从最后一个值开始add到list),跳出递归的条件式链表的next为空,返回list。

import java.util.*;
public class Solution {
    ArrayList<Integer> list = new ArrayList();
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        if(listNode!=null){
        //因为这里用的是递归,所以方法中会从list的最后一个非空值开始存,直到第一个。
            printListFromTailToHead(listNode.next);
            list.add(listNode.val);
        }
        return list;
    }
}

复杂度

时间,空间复杂度为O(n)

思路2:非递归

创建一个list,直接将每次链表next得到的值插入到list的首位–>使用list.add(0,value)的方式。

import java.util.ArrayList;
public class Solution {
        ArrayList<Integer> list = new ArrayList<Integer>();
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
    //注意这里是while(和上面的递归区分一下)
        while(listNode!=null){
            list.add(0,listNode.val);
            listNode = listNode.next;
        }
        return list;
    }
}

复杂度

时间,空间复杂度为O(n)

思路3:

反转链表,将链表的值放到list中,然后直接反转list即可。

import java.util.ArrayList;
import java.util.Collections;
public class Solution {
        ArrayList<Integer> list = new ArrayList<Integer>();
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
while(listNode!=null){
            list.add(listNode.val);
            listNode=listNode.next;
        }
        Collections.reverse(list);
        return list;
    }
    }

5.重建二叉树

思路1:

例如:
前序序列{1,2,4,7,3,5,6,8} = pre
中序序列{4,7,2,1,5,3,8,6} = in
通俗理解:
1)根据先序遍历找到根节点,然后在中序遍历中找对这个根节点,中序遍历左边的所有节点都是根的左子树,右边为右子树。
2)确定左子树的子节点:根据中序遍历里根节点以左的所有节点,找到先序遍历里顺序排在前面的第一个节点,即为该根节点的左子树
3)然后以该左子树作为根,重复1)和2)步骤即可。

代码理解:
1)在先序中找到根节点,对应找到该节点在中序遍历中的位置
2)确定根的左子树:左子树前序遍历{2,4,7},中序遍历为{4,7,2};确定跟的右子树:右子树前序遍历{3,5,6,8},中序遍历为{5,3,8,6}
3)子树同上述操作

import java.util.Arrays;
public class Solution {
    public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
        if (pre.length == 0 || in.length == 0) {
            return null;
        }
        TreeNode root = new TreeNode(pre[0]);
        // 在中序中找到前序的根
        for (int i = 0; i < in.length; i++) {
            if (in[i] == pre[0]) {
                // 左子树,注意 copyOfRange 函数,左闭右开
                root.left = reConstructBinaryTree(Arrays.copyOfRange(pre, 1, i + 1), Arrays.copyOfRange(in, 0, i));
                // 右子树,注意 copyOfRange 函数,左闭右开
                root.right = reConstructBinaryTree(Arrays.copyOfRange(pre, i + 1, pre.length), Arrays.copyOfRange(in, i + 1, in.length));
                break;
            }
        }
        return root;
    }
}

复杂度

时间,空间复杂度为O(n)

6.二叉树的下一个结点(#)

思路:

我们首先要根据给定输入中的结点指针向父级进行迭代,直到找到该树的根节点;然后根据根节点进行中序遍历,当遍历到和给定树节点相同的节点时,下一个节点就是我们的目标返回节点。
具体做法:
step 1:首先先根据当前给出的结点找到根节点
step 2:然后根节点调用中序遍历
step 3:将中序遍历结果存储下来
step 4:最终拿当前结点匹配是否有符合要求的下一个结点

import java.util.*;
public class Solution {
    ArrayList<TreeLinkNode> nodes = new ArrayList<>();
    public TreeLinkNode GetNext(TreeLinkNode pNode) {
        // 获取根节点
        TreeLinkNode root = pNode;
        while(root.next != null) root = root.next;
         
        // 中序遍历打造nodes
        InOrder(root);
         
        // 进行匹配
        int n = nodes.size();
        for(int i = 0; i < n - 1; i++) {
            TreeLinkNode cur = nodes.get(i);
            if(pNode == cur) {
                return nodes.get(i+1);
            }
        }
        return null;
    }
     
    // 中序遍历
    void InOrder(TreeLinkNode root) {
        if(root != null) {
            InOrder(root.left);
            //将中序遍历的结果存起来
            nodes.add(root);
            InOrder(root.right);
        }
    }
}

复杂度

时间,空间复杂度为O(n)

7.用两个栈实现队列

思路:

1)往队列尾部插入值时,对应的栈操作为:直接都压入stack1
2) 往队列头部删除值时:

  • 如果stack2位空,先将stack1的所有值压入stack2,然后弹出stack2的栈顶;
  • 如果stack2不为空,直接弹出栈顶即为删除队头。
import java.util.Stack;

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() {
        //如果stack2为空,把stack1所有的值弹出放到stack2
        if(stack2.size()==0){
            //注意用while
            while(stack1.size()>0){
                stack2.push(stack1.pop());
            }
        }
           return stack2.pop();
    }
}

复杂度

时间:push的时间复杂度为O(1),pop的时间复杂度为O(n),push是直接加到栈尾,相当于遍历了两次栈
空间:O(n),借助了另一个辅助栈空间

8.斐波那契数列

动态规划算法的基本思想:将待求解的问题分解成若干个相互联系的子问题,先求解子问题,然后从这些子问题的解得到原问题的解;对于重复出现的子问题,只在第一次遇到的时候对它进行求解,并把答案保存起来,让以后再次遇到时直接引用答案,不必重新求解。

思路:

每次就用到了最近的两个数,所以我们可以只存储最近的两个数。
sum 存储第 n 项的值
one 存储第 n-1 项的值
two 存储第 n-2 项的值
在这里插入图片描述

public class Solution {
    public int Fibonacci(int n) {
        if(n == 0){
            return 0;
        }else if(n == 1){
            return 1;
        }
        int sum = 0;
        int two = 0;
        int one = 1;
        for(int i=2;i<=n;i++){
            sum = two + one;
            two = one;
            one = sum;
        }
        return sum;
    }
}

9.旋转数组的最小数字

思路:(二分法)

1)设置数组的首尾位置作为区间端点
2)取区间中点,若大于末尾端点,则最小值一定在中点右边;
3)中点若等于末尾端点,不确定哪边的小(1,0,1,1,1或者1,1,1,0,1)所以末尾端点向前推一位;
4)中间若小于末尾端点,最小值一定在中点左边或者中点

import java.util.ArrayList;
public class Solution {
    public int minNumberInRotateArray(int [] array) {
        int left = 0;
        int right = array.length - 1;
        while(left < right){
            int mid = (left + right) / 2;
            //最小的数字在mid右边
            if(array[mid] > array[right])
                left = mid + 1;
            //无法判断,一个一个试
            else if(array[mid] == array[right])
                right--;
            //最小数字要么是mid要么在mid左边(注意这里的mid不能减1,特例:1,0,1,1,1)
            else if(array[mid] < array[right])
                right = mid;
        }
        return array[left];
    }
}

复杂度

时间:O(log2n),二分法最坏情况对nnn取2的对数。
空间:O(1),常数级变量,无额外辅助空间。

10.矩阵中的路径(##)

思路(递归,回溯法):

要在矩阵中找到从某个位置开始,位置不重复的一条路径,路径为某个字符串,那我们肯定是现在矩阵中找到这个位置的起点。没办法直观的找出来,只能遍历矩阵每个位置都当作起点试一试。找到起点后,它周围的节点是否可以走出剩余字符串子串的路径,该子问题又可以作为一个递归。因此,可以用递归来解决。

import java.util.*;
public class Solution {
    private boolean dfs(char[][] matrix, int n, int m, int i, int j, String word, int k, boolean[][] flag){
        if(i < 0 || i >= n || j < 0 || j >= m || (matrix[i][j] != word.charAt(k)) || (flag[i][j] == true))
            //下标越界、字符不匹配、已经遍历过不能重复
            return false;
        //k为记录当前第几个字符
        if(k == word.length() - 1)
            return true;
        flag[i][j] = true;
        //该结点任意方向可行就可
        if(dfs(matrix, n, m, i - 1, j, word, k + 1, flag)
          ||dfs(matrix, n, m, i + 1, j, word, k + 1, flag)
          ||dfs(matrix, n, m, i, j - 1, word, k + 1, flag)
          ||dfs(matrix, n, m, i , j + 1, word, k + 1, flag))
            return true;
        //没找到经过此格的,此格未被占用
        flag[i][j] = false;
        return false;
    }
     
    public boolean hasPath (char[][] matrix, String word) {
        //优先处理特殊情况
        if(matrix.length == 0)
            return false;
        int n = matrix.length;
        int m = matrix[0].length;
        //初始化flag矩阵记录是否走过
        boolean[][] flag = new boolean[n][m];
        //遍历矩阵找起点
        for(int i = 0; i < n; i++){ 
            for(int j = 0; j < m; j++){
                //通过dfs找到路径
                if(dfs(matrix, n, m, i, j, word, 0, flag))
                    return true;
            }
        }
        return false;
    }
}

复杂度

时间:O(mn∗3k),其中mmm与nnn为矩阵的边长,kkk为字符串的长度,遍历一次矩阵,每次条递归最坏遍历深度为kkk,看起来是四个方向,但是有一个方向是来的方向不重复访问,所以是三叉树型递归,因此递归复杂度为O(k3)
空间:O(mn),辅助二维数组记录是否走过某节点使用了空间。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/73330.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

软件测试有哪些常用的测试方法?

软件测试是软件开发过程中重要组成部分&#xff0c;是用来确认一个程序的质量或者性能是否符合开发之前提出的一些要求。软件测试的目的有两方面&#xff0c;一方面是确认软件的质量&#xff0c;另一方面是提供信息&#xff0c;例如&#xff0c;给开发人员或者程序经理反馈意见…

4.MyBatis映射

需求分析 1.订单商品数据模型 (1).表 用户表user:记录了购买商品的用户信息 订单表orders:记录了用户所创建的订单信息 订单明细表orderdetail:记录了订单的详细信息 商品表item:记录了商品详细信息 (2).表与表之间的业务关系 在分析表与表之间的业务关系时&#xff0c;需要建…

Nginx的反向代理和负载均衡

Nginx&#xff1a; Nginx作为面试中的大…小头目&#xff0c;自然是不能忽视的&#xff0c;而以下两点就是它能成为面试中头目的招牌。 反向代理和负载均衡 在此之前&#xff0c;我们先对Nginx做一个简单的了解 Nginx概述&#xff1a; Nginx (engine x) 是一个高性能的HTTP…

Ansible——inventory 主机清单

Ansible——inventory 主机清单Ansible——inventory 主机清单inventory简介ansible配置文件的优先级ansible命令常用参数主机清单文件hosts&#xff08;/etc/ansible/hosts&#xff09;通过列表的方式标识主机范围指定主机端口使用主机名表示主机范围inventory 中的变量主机变…

JS 数组方法 every 和 some 的区别

1. 前言 2. every 和 some 相同点 3. every 和 some 的区别 4. every 和 some 总结 1. 前言 JS 数组方法 every 和 some 的区别 &#xff1f; 这是某位前端玩家遇到的面试题 特定场景合理的使用 JS 方法&#xff0c;不仅可以减少我们的代码量&#xff0c;还能更轻松的阅读…

宇航服,真正的“科技”与“狠活”!

千百年的探索仰望和摘星的遐想&#xff0c;已照进现实&#xff0c;浩瀚的天宫&#xff0c;我们亦可置身其中。 北京时间2022年12月4日20时09分&#xff0c;神舟十四号载人飞船返回舱在东风着陆场成功着陆&#xff0c;标志着太空出差183天的宇航员正式回家&#xff01;据悉&…

基于PCA 和迭代 Canny Edge皮肤病变分割算法研究附Matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

NLP_learning 中文基本任务与处理(分词、停用词、词性标注、语句依存分析、关键词抽取、命名实体识别)介绍、jieba工具库

文章目录1、分词2、停用词和N-gram停用词N-gram3、 更多任务&#xff08;词性标注、依赖分析、NER、关键词抽取&#xff09;词性标注句法依存分析命名实体识别关键词抽取4、 jieba工具库使用&#xff08;1&#xff09;基本分词函数与用法&#xff08;2&#xff09;词性标注&…

【LeetCode】C++:数组类算法-双索引技巧-对撞指针

目录 167. 两数之和 II - 输入有序数组 125.验证回文串 345.反转字符串中的元音字母 11.盛最多水的容器 209.长度最小的数组 167. 两数之和 II - 输入有序数组 给你一个下标从1开始的整数数组 numbers &#xff0c;该数组已按非递减顺序排列 &#xff0c;请你从数组中找出…

视频剪辑软件哪个好用?快把这些软件收好

现如今自媒体行业正在如火如荼的发展&#xff0c;越来越多的人加入进视频剪辑的队伍中。小伙伴们也有萌生想要剪辑视频的念头吗&#xff1f;大家是否苦于不知道该如何视频剪辑呢&#xff1f;为了帮助大家解决这个问题&#xff0c;今天我就来为大家教几种不错的剪辑方法&#xf…

YOLOv5图像分割中的NMS处理

在上一篇文章YOLOv5图像分割--SegmentationModel类代码详解有讲到图像经过YOLOv5网络后得到的输出形式&#xff0c;主要是调用了BaseModel类下的forward得到的输出&#xff0c;输出的shape为【batch,25200,117】&#xff0c;这里的25200相当于总的anchors数量【以640*640的输入…

vuex原理和下载

vuex&#xff1a;状态管理模式 vue全家桶&#xff1a;vue-cli&#xff08;脚手架&#xff09;、vue-router&#xff08;路由管理器&#xff09;、vuex&#xff08;状态管理模式&#xff09; 原理图示&#xff1a; 原理描述&#xff1a; vuex在vue组件外面进行组件状态的管理…

引用的小细节内联函数

1.引用的细节 引用&#xff0c;简单来说就是“取别名”。既然是别名&#xff0c;那么引用就一定具有以下的特点 引用在定义时必须初始化。 就好比起别名起码得告诉别人是给谁起的别名吧 一个变量可以有多个引用 就好比一个人可以有多个别名。比如张某某&#xff0c;有两个外号…

智慧农业创造新兴业态,推动农业产业现代化步伐

农业是国民经济的基础&#xff0c;在国家经济发展中起着不可替代的作用&#xff0c;随着物联网、人工智能、信息技术的快速发展&#xff0c;农业逐渐走向智能化、现代化和自动化&#xff0c;智慧农业已经深入到农业生产的各个环节&#xff0c;成为了现代农业发展新的方向。 所谓…

JAVA12_08学习总结(CSS)

今日内容 1. frameset 框架集标签frameset框架集标签不能放在body中rows--划分页面为上下部分cols--划分页面为左右部分框架标签frame框架的名称name属性<frame src"#" name"#" />src后代表这个框架中打开的页面链接name后代表这个被打开页面的nam…

JavaScript -- 11. BOM及常用对象介绍

文章目录BOM对象1 BOM2 navigator3 location3.1 常用方法3.2 url各部分名称4 historyBOM对象 1 BOM 浏览器对象模型 BOM为我们提供了一组对象&#xff0c;通过这组对象可以完成对浏览器的各种操作 BOM对象&#xff1a; Window —— 代表浏览器窗口&#xff08;全局对象&…

哥斯拉连webshell需要配置(哥斯拉连接Webshell实践)

1. 哥斯拉连webshell需要配置环境 kali linux   docker+vulhub   nginx(1.19.6)+php(7.4.15) 2. 哥斯拉连webshell需要配置过程 2.1 vulhub镜像拉取 vulhub安装的话去官网上有安装教程   Vulhub - Docker-Compose file for vulnerability environment   安装好之后…

VUE基本认知

1&#xff1a;vue介绍 渐进式 JavaScript 框架&#xff08;有2个库&#xff0c;核心库和插件库&#xff0c;如果能用核心库解决的就是用核心库&#xff0c;核心库解决不了的&#xff0c;就使用插件库&#xff09; 渐&#xff1a;逐渐&#xff0c; 进&#xff1a;添加 作者: 尤…

原生数据湖体系

背景&#xff1a; 随着数据量的爆发式增长&#xff0c;数字化转型称为了整个IT行业的热点&#xff0c;数据也开始需要更深度的价值挖掘&#xff0c;因此需要确保数据中保留的原始信息不丢失&#xff0c;从而应对未来不断变化的需求。当前以oracle为代表的数据库中间件已经逐渐…

nginx详细配置负载均衡全过程以及宕机情况处理

一、准备 1.下载安装nginx服务器&#xff08;win10/Linux同样适用&#xff09; 2.两个以上服务的服务地址 二、详细步骤以及宕机情况处理 &#xff08;1&#xff09;编辑 nginx.conf 配置文件&#xff0c;该文件在conf文件夹下面。 轮询&#xff1a; upstream my_server …