java leetcode_剑指offer刷题(上)--Java版(LeetCode)

剑指 offer 03 数组中重复的数字

题目

在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

思路

法一:利用hashset的不重复性

查找重复的数字,利用哈希集合(HashSet)的特性不可重复性,如果set中没有添加成功,说明有重复,即可解决,类似查重的都可以利用hashset,而对应的HashMap可以重复

法二:利用动态数组

剑指 offer 04 二维数组中的查找

题目

在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

思路

如下图,将该数组倒过来,选取下顶点(i=length-1,j=0)的值,若比该值比目标值小,则应该减小行数,若比目标大,则增加列数,即可最快找到目标值

class Solution {

public boolean findNumberIn2DArray(int[][] matrix, int target) {

int i = matrix.length - 1, j = 0;//选取第一列最后一个数 while(i >= 0 && j < matrix[0].length)

{

if(matrix[i][j] > target) i--;//大于目标值,减少行数 else if(matrix[i][j] < target) j++;//小于目标值,增加列数 else return true;//等于返回true }

return false;

}

}

剑指 offer 05 替换空格

题目

请实现一个函数,把字符串 s 中的每个空格替换成"%20"。

思路

法一:利用replaceAll( )方法,但是不适用于面试和笔试

法二:建立新的String利用If判断进行添加,一般使用StringBuilder(效率高)或者StringBuffer(线程安全)

法三:对数组进行扩容,进行逐个添加,特定字符替换,arr[size++]体现了动态的将数据加入到数组中,同时String(arr,offset,count)方法注意一下

剑指 offer 06 从尾到头打印链表

题目

输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。

思路

法一:属于逆序的应用,应该首先想到栈,先进后出,先入栈

法二:还可以创建一个同大小新的数组,从数组的尾部进行添加数据,也可以得到逆序

剑指 offer 07 重建二叉树

题目

输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

思路

知识点:前序遍历列表:第一个元素永远是 【根节点 (root)】

中序遍历列表:根节点 (root)【左边】的所有元素都在根节点的【左分支】,【右边】的所有元素都在根节点的【右分支】

递推参数: 根节点在前序遍历的索引 root 、子树在中序遍历的左边界 left 、子树在中序遍历的右边界 right ;

终止条件: 当 left > right ,代表已经越过叶节点,此时返回 null;

递推工作:

建立根节点 node : 节点值为 preorder[root] ; 划分左右子树: 查找根节点在中序遍历 inorder 中的索引 i ; 为了提升效率,使用哈希表 dic 存储中序遍历的值与索引的映射,查找操作的时间复杂度为 O(1)O(1)

构建左右子树: 开启左右子树递归;

/*class TreeNode{int val;TreeNode left;TreeNode right;TreeNode(int x){ val = x;}}*/

public TreeNode buildTree(int[] preorder, int[] inorder) {

//保存中序遍历的下标和数据在map中,便于提高查找效率,注意要反过来保存 HashMap map = new HashMap<>();

for (int i=0;i

map.put(inorder[i],i);

}

TreeNode root = TreeBuild(preorder, map, 0, 0, map.size() - 1);

return root;

}

/*** @describe: 恢复二叉树* @param preOrder 前序遍历* @param map 保存中序遍历的哈希表* @param rootIndex 根节点下标(前序遍历中* @param leftIndex 左边界* @param rightIndex 右边界* @return: jianzhioffer.TreeNode* @author Eric:* @date: 2020/11/3 23:58*/

public TreeNode TreeBuild(int[] preOrder,HashMap map,int rootIndex,int leftIndex,int rightIndex){

//递归退出条件 if (leftIndex>rightIndex){

return null;

}

//建立新的根节点,根节点下标为前序遍历中根节点下标 TreeNode root = new TreeNode(preOrder[rootIndex]);

//得出根节点在中序遍历中的下标 int idx = map.get(root.val);

//进行左子树递归,对于左子树(中序遍历中),其根节点(前序遍历)即为前序遍历中右子树的第一个节点,即为rootIndex+1 //其左边界(中序遍历)为原边界leftIndex,右边界为左子树(中序遍历)的边界,即为idx-1 root.left = TreeBuild(preOrder,map,rootIndex+1,leftIndex,idx-1);

//进行右子树递归,对于右子树(中序遍历中),其根节点(前序遍历中)当前根节点+左子树的个数+1rootIndex+idx-leftIndex+1 //左边界(中序遍历)为右子树(中序遍历中)的第一个节点idx+1,右节点不变rightIndex root.right = TreeBuild(preOrder,map,(rootIndex+idx-leftIndex+1),idx+1,rightIndex);

return root;

}

剑指 offer 09 用两个栈实现队列

题目

用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )

思路分析:

栈stack1用来添加数据,另一个栈stack2用来删除数据,当删除数据时,注意,先判断stack2是否为空,因为只有stack2为空,才能把stack1的数据添加进去,不会扰乱顺序 ,stack2中的数据具有删除优先级,相当于队列头部数据

(1)stack2空,stack1空,删除后剩余数据为0,添加的数据为0,所以返回-1

(2)stack2空,stack1不空,则将stack1的所有数据压入stack2,再返回stack2.pop()的数据

(3)stack2不空,则不管stack1是否有没有数据,只能删除stack2的数据删除完,返回stack2.pop()的数据

优化

如果你使用Stack的方式来做这道题,会造成速度较慢; 原因的话是Stack继承了Vector接口,而Vector底层是一个Object[]数组,那么就要考虑空间扩容和移位的问题了。 可以使用LinkedList来做Stack的容器,因为LinkedList实现了Deque接口,所以Stack能做的事LinkedList都能做,其本身结构是个双向链表,扩容消耗少。

LinkedList stack1 = new LinkedList;

LinkedList stack2 = new LinkedList;

Deque stack1 = new LinkedList;

Deque stack2 = new LinkedList;

剑指 offer 10-1 斐波那契数列

题目

写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 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和n>2,按照公式进行计算即可

注意在n>2进行循环进行计算时,数字会越来越大,超越32位甚至64位,所以在循环内就需要边计算边取模,防止出界

法一:动态规划

原理: 以斐波那契数列性质 f(n + 1) = f(n) + f(n - 1)为转移方程。 从计算效率、空间复杂度上看,动态规划是本题的最佳解法。

法二:递归

原理: 把 f(n) 问题的计算拆分成 f(n-1) 和 f(n−2) 两个子问题的计算,并递归,以 f(0) 和 f(1) 为终止条件。 缺点: 大量重复的递归计算,例如 f(n)和 f(n - 1)两者向下递归需要 各自计算 f(n - 2)的值。

剑指 offer 10-2 青蛙跳台阶问题

题目

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

思路

设跳上 n 级台阶有 f(n)种跳法。在所有跳法中,青蛙的最后一步只有两种情况: 跳上 1级或 2级台阶。

当为 1 级台阶: 剩 n-1个台阶,此情况共有 f(n-1) 种跳法; 当为 2级台阶: 剩 n-2个台阶,此情况共有 f(n-2)种跳法。

f(n) 为以上两种情况之和,即 f(n)=f(n-1)+f(n-2),以上递推性质为斐波那契数列。

剑指 offer 11 旋转数组的最小数字

题目

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。

思路

法一:常规思路,复杂度为O(n),遍历数组,当找到当前数小于前一个数,最小数为当前数,如果遍历完没有找到,就返回第一个元素(说明数组没有旋转,顺序为从小到大)

法二:利用二分查找法,复杂度为log(n),推荐使用,左边界为Low,右边界为high,中间为mid=(low+high)/2

class Solution {

public:

int minArray(vector& numbers) {

int low = 0;

int high = numbers.size() - 1;

while (low < high) {//当low==high说明已经找到 int pivot = low + (high - low) / 2;

if (numbers[pivot] < numbers[high]) {

high = pivot;

}

else if (numbers[pivot] > numbers[high]) {

low = pivot + 1;

}

else {//相等的情况,high左移 high -= 1;

}

}

return numbers[low];

}

};

剑指 offer 12 矩阵中的路径

题目

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。

[["a","b","c","e"], ["s","f","c","s"], ["a","d","e","e"]]

思路

本问题是典型的矩阵搜索问题,可使用 深度优先搜索(DFS)+ 剪枝 解决。

每个点有4个方向可以走,可以想象成一棵4叉树,树的深度优先遍历很容易想到用递归,当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕。

算法原理

深度优先搜索: 可以理解为暴力法遍历矩阵中所有字符串可能性。DFS 通过递归,先朝一个方向搜到底,再回溯至上个节点,沿另一个方向搜索,以此类推。 剪枝: 在搜索中,遇到 这条路不可能和目标字符串匹配成功 的情况(例如:此矩阵元素和目标字符不同、此元素已被访问),则应立即返回,称之为 可行性剪枝 。

递归参数: 当前元素在矩阵 board 中的行列索引 i 和 j ,当前目标字符在 word 中的索引 k 。 终止条件: 返回 false : ① 行或列索引越界 或 ② 当前矩阵元素与目标字符不同 或 ③ 当前矩阵元素已访问过 (③ 可合并至 ② ) 。 返回 true: 字符串 word 已全部匹配,即 k = len(word) - 1 。 递推工作: 标记当前矩阵元素: 将 board[i] [j]值暂存于变量 tmp ,并修改为字符 '/' ,代表此元素已访问过,防止之后搜索时重复访问。 搜索下一单元格: 朝当前元素的 上、下、左、右 四个方向开启下层递归,使用 或 连接 (代表只需一条可行路径) ,并记录结果至 res 还原当前矩阵元素: 将 tmp 暂存值还原至 board[i] [j] 元素。 回溯返回值: 返回 res ,代表是否搜索到目标字符串。

class Solution {

public boolean exist(char[][] board, String word) {

char[] words = word.toCharArray();

for(int i = 0; i < board.length; i++) {

for(int j = 0; j < board[0].length; j++) {

if(dfs(board, words, i, j, 0)) return true;

}

}

return false;

}

boolean dfs(char[][] board, char[] word, int i, int j, int k) {

if(i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != word[k]) return false;

if(k == word.length - 1) return true;

char tmp = board[i][j];

board[i][j] = '/';

boolean res = dfs(board, word, i + 1, j, k + 1) || dfs(board, word, i - 1, j, k + 1) ||

dfs(board, word, i, j + 1, k + 1) || dfs(board, word, i , j - 1, k + 1);

board[i][j] = tmp;//回溯 return res;

}

}

剑指 offer 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。请问该机器人能够到达多少个格子?

思路

很明显本题采用dfs遍历,同时要注意判断是否已经被访问过

class Solution {

public int movingCount(int m, int n, int k) {

int[][] arr = new int[m][n];

boolean[][] isVisited = new boolean[m][n];

return dfs(arr,0,0,isVisited,k);

}

public int dfs(int[][] arr,int i,int j,boolean[][] isVisited,int k){

//判断当前位置是否符合访问的规则 if (i<0||j<0||i>=arr.length||j>=arr[0].length||isVisited[i][j]){

return 0;

}

int sum = i/10+i%10+j/10+j%10;//得到当前位置的sum和 if (sum>k){

return 0;

}

isVisited[i][j] = true;//设置当前位置访问过 //最后的结果+1是因为当前位置也可以访问到 int res = dfs(arr,i-1,j,isVisited,k)+dfs(arr,i,j-1,isVisited,k)+

dfs(arr,i+1,j,isVisited,k)+dfs(arr,i,j+1,isVisited,k)+1;

return res;

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SQLAlchemy 是一个 SQL 工具包和对象关系映射(ORM)库,用于 Python 编程语言。它提供了一个高级的 SQL 工具和对象关系映射工具,允许开发者以 Python 类和对象的形式操作数据库,而无需编写大量的 SQL 语句。SQLAlchemy 建立在 DBAPI 之上,支持多种数据库后端,如 SQLite, MySQL, PostgreSQL 等。 SQLAlchemy 的核心功能: 对象关系映射(ORM): SQLAlchemy 允许开发者使用 Python 类来表示数据库表,使用类的实例表示表中的行。 开发者可以定义类之间的关系(如一对多、多对多),SQLAlchemy 会自动处理这些关系在数据库中的映射。 通过 ORM,开发者可以像操作 Python 对象一样操作数据库,这大大简化了数据库操作的复杂性。 表达式语言: SQLAlchemy 提供了一个丰富的 SQL 表达式语言,允许开发者以 Python 表达式的方式编写复杂的 SQL 查询。 表达式语言提供了对 SQL 语句的灵活控制,同时保持了代码的可读性和可维护性。 数据库引擎和连接池: SQLAlchemy 支持多种数据库后端,并且为每种后端提供了对应的数据库引擎。 它还提供了连接池管理功能,以优化数据库连接的创建、使用和释放。 会话管理: SQLAlchemy 使用会话(Session)来管理对象的持久化状态。 会话提供了一个工作单元(unit of work)和身份映射(identity map)的概念,使得对象的状态管理和查询更加高效。 事件系统: SQLAlchemy 提供了一个事件系统,允许开发者在 ORM 的各个生命周期阶段插入自定义的钩子函数。 这使得开发者可以在对象加载、修改、删除等操作时执行额外的逻辑。
SQLAlchemy 是一个 SQL 工具包和对象关系映射(ORM)库,用于 Python 编程语言。它提供了一个高级的 SQL 工具和对象关系映射工具,允许开发者以 Python 类和对象的形式操作数据库,而无需编写大量的 SQL 语句。SQLAlchemy 建立在 DBAPI 之上,支持多种数据库后端,如 SQLite, MySQL, PostgreSQL 等。 SQLAlchemy 的核心功能: 对象关系映射(ORM): SQLAlchemy 允许开发者使用 Python 类来表示数据库表,使用类的实例表示表中的行。 开发者可以定义类之间的关系(如一对多、多对多),SQLAlchemy 会自动处理这些关系在数据库中的映射。 通过 ORM,开发者可以像操作 Python 对象一样操作数据库,这大大简化了数据库操作的复杂性。 表达式语言: SQLAlchemy 提供了一个丰富的 SQL 表达式语言,允许开发者以 Python 表达式的方式编写复杂的 SQL 查询。 表达式语言提供了对 SQL 语句的灵活控制,同时保持了代码的可读性和可维护性。 数据库引擎和连接池: SQLAlchemy 支持多种数据库后端,并且为每种后端提供了对应的数据库引擎。 它还提供了连接池管理功能,以优化数据库连接的创建、使用和释放。 会话管理: SQLAlchemy 使用会话(Session)来管理对象的持久化状态。 会话提供了一个工作单元(unit of work)和身份映射(identity map)的概念,使得对象的状态管理和查询更加高效。 事件系统: SQLAlchemy 提供了一个事件系统,允许开发者在 ORM 的各个生命周期阶段插入自定义的钩子函数。 这使得开发者可以在对象加载、修改、删除等操作时执行额外的逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值