剑指offer03 数组中重复的数字
思路: 只要数组中的数字是有范围的,而且是与数组长度大小是有关系的,这时脑子中的第一思路就是用数组来模拟哈希表,简称数组hash,数组hash就是这个数组中的下标值就是原数组中的值
剑指offer04 二维数组中查找
思路:找到最后一列的值,从上到下进行比较,如果target小于这个值,说明,target在以这个值为右下角的矩形当中。如果target大于这个值,说明target在以这个值为右下角的矩形的下面!!!
剑指offer05 替换空格
思路:遍历s字符串,遇到空格,就append("%20"),很简单的一道题
剑指offer06 从尾到头打印链表
思路: 使用递归,借用系统栈,即可。
class Solution {
int[] res;
int len=0;
int index=0;
public int[] reversePrint(ListNode head) {
if(head==null){
len = index;
return new int[len];
}
index++;
res = reversePrint(head.next);
index--;
res[len-index-1] = head.val;
return res;
}
}
剑指offer07 重建二叉树
思路: 关键是从先序遍历的第一个节点是根节点,然后中序遍历中这个第一个节点前面的所有节点都是这个根节点的左子树中的节点,这样就可以递归了
Arrays.copyOfRange这个方法的返回值是一个重新的数组,而且是按照索引,且顾头不顾尾,右边的那个索引值不会取到
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
int n = preorder.length;
if (n == 0)
return null;
int rootVal = preorder[0], rootIndex = 0;
for (int i = 0; i < n; i++) {
if (inorder[i] == rootVal) {
rootIndex = i;
break;
}
}
TreeNode root = new TreeNode(rootVal);// 注意注意Arrays.copyOfRange是以索引,而且顾头不顾尾,右边的那个索引值不取
root.left = buildTree(Arrays.copyOfRange(preorder, 1, 1 + rootIndex), Arrays.copyOfRange(inorder, 0, rootIndex));
root.right = buildTree(Arrays.copyOfRange(preorder, 1 + rootIndex, n), Arrays.copyOfRange(inorder, rootIndex + 1, n));
return root;
}
}
剑指offer09 用两个栈实现队列
思路:就想用两个杯子,互相倒开水,每次取数,你都要把杯子中的水全部倒到另外一个杯子中
class CQueue {
LinkedList<Integer> stack1;
LinkedList<Integer> stack2;
public CQueue() {
stack1 = new LinkedList<>();
stack2 = new LinkedList<>();
}
// pushLast
public void appendTail(int value) {
stack1.add(value);
}
// getFirst
public int deleteHead() {
if (stack2.isEmpty()) {
if (stack1.isEmpty()) return -1;
while (!stack1.isEmpty()) {
stack2.add(stack1.pop());
}
return stack2.pop();
}
else
return stack2.pop();
}
}
剑指offer10-I 斐波那且数列
思路:最简单的动态规划,不说了。。。
剑指offer10-II 青蛙跳台阶
思路:非常简单的动态规划,dp[n] = dp[n-1] + dp[n-2]
剑指offer11 旋转数组的最小数字
关于二分查找的几个注意点,请看我这篇博客
俺的二分查找博客
class Solution {
public int minArray(int[] numbers) {
int len = numbers.length;
int left = 0;
int right=len-1;
while(left<right){
int mid = left + (right-left)/2;
if(numbers[mid]<numbers[right]){
right = mid;
}
else if(numbers[mid]==numbers[right]){
right--;
}
else if(numbers[mid]>numbers[right]){
left = mid + 1;
}
}
return numbers[left];
}
}
剑指offer12、13矩阵中的路径,机器人的运动范围
思路: 都是深度优先索引,这个代码相比于动态规划,真是多了好几倍,不过理解起来却比动态规划简单好几倍。典型的纸老虎
class Solution {
public boolean exist(char[][] board, String word) {
if (board == null || board[0] == null || board.length == 0 || board[0].length == 0 || word == null || word.equals("")) {
return false;
}
boolean[][] isVisited = new boolean[board.length][board[0].length];
char[] chs = word.toCharArray();
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
if (board[i][j] == chs[0]) {
if (dfs(board, i, j, isVisited, chs, 0)) return true;
}
}
}
return false;
}
private boolean dfs(char[][] board, int i, int j, boolean[][] isVisited, char[] chs, int index) {
if (index == chs.length) {
return true;
}
if (i < 0 || j < 0 || i == board.length || j == board[0].length || isVisited[i][j] || board[i][j] != chs[index]) {
return false;
}
isVisited[i][j] = true;
boolean res = dfs(board, i + 1, j, isVisited, chs, index + 1)
|| dfs(board, i - 1, j, isVisited, chs, index + 1)
|| dfs(board, i, j + 1, isVisited, chs, index + 1)
|| dfs(board, i, j - 1, isVisited, chs, index + 1);
isVisited[i][j] = false;
return res;
}
}
机器人:
class Solution {
public int movingCount(int m, int n, int k) {
boolean[][] visited = new boolean[m][n];
return dfs(0, 0, m, n, k, visited);
}
private int dfs(int i, int j, int m, int n, int k, boolean visited[][]) {
if (i < 0 || i >= m || j < 0 || j >= n || (i/10 + i%10 + j/10 + j%10) > k || visited[i][j]) {
return 0;
}
visited[i][j] = true;
return dfs(i + 1, j, m, n, k, visited) + dfs(i - 1, j, m, n, k, visited) +
dfs(i, j + 1, m, n, k, visited) + dfs(i, j - 1, m, n, k, visited) + 1;
}
}
两者都是在一个方格中进行DFS,其实关于dfs的代码,主要就是参数比较多,但是参数其实也是类似的,都要当前在哪个点的横纵坐标,都有方格的长与宽!!!以及visited[ ]数组
剑指offer14-I 减绳子
思路:常规的动态规划,dp[i] 表示整数i,拆分后得到的乘积的最大值,则求dp[n] 必须把n之前的数字都遍历一遍,都拆分看看,前面一部分就看做是已经拆分好的的最大值,后面一部分就单纯的是这一部分到末尾的距离
但这个dp[i]也要可能比i小,因此,还要比较一下
class Solution {
public int integerBreak(int n) {
int[] dp = new int[n+1];
dp[1]=1;
for(int i=2;i<=n;i++){
for(int j=1;j<i;j++){
dp[i] = Math.max(dp[i], Math.max(dp[j],j)*(i-j));
}
}
return dp[n];
}
}
减绳子II (跟上面的题目差不多,只不过有大数)
3
思路:用贪心算法。尽可能多的分出长度为3的字段出来,这样得到的乘积就会最大。证明就省略了,记住这个结论即可
class Solution {
public int cuttingRope(int n) {
if(n < 4){
return n - 1;
}
if(n==4) return 4;
long res = 1;
while(n > 4){
res = res * 3 % 1000000007;
n -= 3;
}
return (int) (res * n % 1000000007);
}
}
剑指offer15 二进制中1的个数
思路:这里考查 n & (n-1) 这个位运算的含义。即将二进制最后一个1变成0
public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
int count=0;
while(n!=0){
count++;
n = n & (n-1);
}
return count;
}
}
剑指offer16 数值的整数次方
思路:简单递归
class Solution {
public double myPow(double x, int n) {
if(n==1) return x;
if(n==0) return 1;
if(n==-1) return 1/x;
double half = myPow(x, n/2);
double mod = myPow(x, n%2);
return half * half * mod;
}
}
剑指offer17 打印1到最大的n位数
思路:在赋值的时候可以一边从前赋值,一边从后赋值
class Solution {
public int[] printNumbers(int n) {
// x = 10的n次方 得到数组长度
int x = (int)Math.pow(10,n);
// 创建一个大小为10的n次方-1的数组
int[] result = new int[x-1];
for(int i=0,j=x-2;i<x-1;i++,j--){
result[i] = i+1;
result[j] = j+1;
if(i==j) break;
}
return result;
}
}
剑指Offer18 删除链表的节点
用双指针:pre cur两个指针,pre指针在cur指针的前面,cur指针用来val节点,pre指针用来重指向
用递归
class Solution {
public ListNode deleteNode(ListNode head, int val) {
// 递归解法
if(head==null) return null;
if(head.val == val) return head.next;
head.next = deleteNode(head.next, val);
return head;
// 双指针解法
/*
if(head == null) return head;
ListNode cur = head;
ListNode pre = null;
if(cur.val == val) return head.next;
while(cur.val != val) {
pre = cur;
cur = cur.next;
}=
pre.next = pre.next.next;
return head;
*/
}
}
剑指offer21 调整数组顺序使得奇数位于偶数前面
思路:有点快速排序的思想,定义双指针,一个从前面,一个从后面,前后夹击,前面定位到偶数,后面定位到奇数,然后就可以进行交换
class Solution {
public int[] exchange(int[] nums) {
int left = 0;
int right = nums.length - 1;
// 前后夹击,太秒了
while (left < right) {
// 从前往后定位到不是奇数的位置
while (left < right && nums[left] % 2 != 0) {
left++;
}
// 从后往前定位到不是偶数的位置
while (left < right && nums[right] % 2 == 0) {
right--;
}
// 将上面定位到的两个位置的数字进行交换
if (left < right) {
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
}
return nums;
}
}
剑指offer22 链表中倒数第k个节点
思路:使用快慢指针,使得快指针领先满指针k个节点,这样快指针到头之后,慢指针就是指向倒数第k个
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode fast = head;
while(fast!=null) {
fast = fast.next;
if(k==0) {
head = head.next;
}else {
k--;
}
}
return head;
}
}