剑指Offer算法题03-10

03. 二维数组中查找

描述:在一个二维数组中,每一行都从小到大排列,每一列都从上到下排列,实现查找目标数字是否在二维数组中存在

思路:从最右一列开始或者最下一行开始,判断目标值是否小于(或者大于)该列(行)第一个值,用于淘汰该列或者该列或者行的依据

代码:

public static boolean exist(int[][] arr, int target) {
  if (arr == null || arr.length == 0 || arr[0].length == 0) {
    return false;
  }
  return process(arr, 0, arr.length - 1, 0, arr[0].length - 1, target);
}
// x 表示行  y 表示列
private static boolean process(int[][] arr, int xStart, int xEnd, int yStart, int yEnd,
    int target) {
  if (xStart > xEnd || yStart > yEnd) {
    return false;
  }
  if (arr[xStart][yEnd] > target) {
    // 裁剪掉最后一列
    return process(arr, xStart, xEnd, yStart, yEnd - 1, target);
  } else if (arr[xStart][yEnd] < target) {
    // 裁剪掉第一行
    return process(arr, xStart + 1, xEnd, yStart, yEnd, target);
  } else {
    return true;
  }
}

public static void main(String[] args) {
  int[][] arr = {{1, 6, 9, 15, 22}, {2, 7, 10, 16, 23}, {5, 8, 11, 17, 24}, {7, 9, 12, 18, 25},
      {10, 13, 17, 19, 28}};
  System.out.println(exist(arr, 11));
  System.out.println(exist(arr, 14));
  System.out.println(exist(arr, 18));
}

04. 替换空格

描述:将字符串中的所有空格替换为%20

思路:遍历,找到空格个数,然后创建一个固定长度的数组,从后向前遍历原字符串,进行字符数组填充

代码:

public static String replace(String str) {
  if (str == null || str.length() == 0) return str;

  int count = 0;
  for(int i = 0; i< str.length(); i++) {
    if(str.charAt(i) == ' ') count++;
  }

  if(count == 0) return str;

  char[] chs = new char[str.length() + count * 2];
  int ptr = chs.length -1;
  for(int i = str.length() -1; i >= 0; i--) {
    if(str.charAt(i) != ' ') {
      chs[ptr--] = str.charAt(i);
    } else {
      chs[ptr--] = '0';
      chs[ptr--] = '2';
      chs[ptr--] = '%';
    }
  }

  return new String(chs);
}

05. 逆序打印链表

描述:从尾倒头打印链表(尾部的元素最先打印出来)

思路:使用栈实现,遍历链表,进行压栈,然后再弹出打印

代码:

// 带头节点
public static void printList1(Node head) {
  if(head == null) return;
  
  Stack<Integer> stack = new Stack<>();
  
  Node ptr = head.next;
  while(ptr != null) {
    stack.push(ptr.data);
    ptr = ptr.next;
  }
  while(!stack.isEmpty()) {
    System.out.print(stack.pop() + "\t");
  }
  System.out.println();
}
// 不带头节点
public static void printList2(Node head) {
  Stack<Integer> stack = new Stack<>();
  while(head != null) {
    stack.push(head.data);
    head = head.next;
  }
  while(!stack.isEmpty()) {
    System.out.print(stack.pop() + "\t");
  }
  System.out.println();
}

06. 重建二叉树

描述:输入谋二叉树的先序遍历和中序遍历的结果,请重建出二叉树(不含重复元素)

思路:采用递归方式构建,取先序遍历的第一个元素,在中序遍历中找到它,它前面的元素用于构建左子树,它后面的元素用于构建右子树,依次递归,得到二叉树。

代码:

static class BinaryTreeNode {

  public Integer data;
  public BinaryTreeNode left;
  public BinaryTreeNode right;

  public BinaryTreeNode(Integer data) {
    this.data = data;
  }
}

public static BinaryTreeNode createBinaryTree(int[] preOrder, int[] inOrder) {
  if (preOrder == null || inOrder == null || preOrder.length == 0 || inOrder.length == 0
      || preOrder.length != inOrder.length) {
    return null;
  }
  return process(preOrder, 0, preOrder.length - 1, inOrder, 0, inOrder.length - 1);
}

public static BinaryTreeNode process(int[] preOrder, int preStart, int preEnd,
    int[] inOrder, int inStart, int inEnd) {
  if (preStart > preEnd || inStart > inEnd) {
    return null;
  }
  int rootData = preOrder[preStart];
  BinaryTreeNode root = new BinaryTreeNode(rootData);
  // 找到中序遍历中root的位置
  int count = 0;
  for (int i = inStart; i <= inEnd; i++) {
    if (rootData == inOrder[i]) {
      break;
    }
    count++;
  }
  root.left = process(
      preOrder, preStart + 1, preStart + count,
      inOrder, inStart, inStart + count - 1);
  root.right = process(
      preOrder, preStart + count + 1, preEnd,
      inOrder, inStart + count + 1, inEnd);

  return root;
}

07. 栈模仿队列

描述:用两个栈实现队列,需要实现两个函数,appendTail和deleteHead

思路:需要注意一个原则,就是stackPop只有在没有元素的时候才能倒入stackPush的元素,并且一次性倒完。

代码:

public static class TwoStacksQueue {

  public Stack<Integer> stackPush;
  public Stack<Integer> stackPop;

  public TwoStacksQueue() {
    stackPush = new Stack<>();
    stackPop = new Stack<>();
  }

  // 进队列
  public void appendTail(Integer value) {
    this.stackPush.push(value);
  }

  // 出队列
  public Integer deleteHead() {
    // 弹出队列没有数据,则从stackPush倒入数据
    if (this.stackPop.isEmpty()) {
      while (!this.stackPush.isEmpty()) {
        this.stackPop.push(this.stackPush.pop());
      }
      if (this.stackPop.isEmpty()) {
        throw new RuntimeException("没有元素弹出");
      }
    }
    return this.stackPop.pop();
  }

  // 获取队列对首元素
  public Integer peek() {
    if (this.isEmpty()) {
      throw new RuntimeException("队列为空");
    }
    pushToPop();
    return this.stackPop.peek();
  }

  // 两个栈倒元素
  private void pushToPop() {
    if (stackPop.empty()) {
      while (!stackPush.empty()) {
        stackPop.push(stackPush.pop());
      }
    }
  }

  public boolean isEmpty() {
    return this.stackPop.isEmpty() && this.stackPush.isEmpty();
  }
}

08. 旋转数组的最小数字

描述:把一个数组的最开始的若干个元素搬到数组尾部,称为数组的旋转。输入一个递增数字的旋转,输入旋转数组的最小元素。例如数组{3,4,5,1,2}是{1,2,3,4,5}的一个数组旋转,该数组的最小元素是1。

思路:采用二分法进行查找

public static int getMinElement(int[] arr) {
  if (arr == null || arr.length == 0) {
    throw new RuntimeException("array is empty.");
  }
  return process(arr, 0, arr.length - 1);
}

private static int process(int[] arr, int L, int R) {
  if (R - L == 1 || R == L) {
    return arr[R];
  }

  int M = L + ((R - L) >> 1);
  if (arr[M] > arr[R]) {
    return process(arr, M, R);
  } else {
    return process(arr, L, M);
  }
}

09. 斐波拉契数列

描述:输入一个n,求fibonacci的第n项。{0,1,1,2,3,5,8,13,21,34,55,…}

// 效率低下方式
public static long fibonacci1(int n) {
  if(n <= 0 ) return 0;
  if(n == 1) return 1;
  return fibonacci1(n-1) + fibonacci1(n-2);
}

思路:消除重复计算,将计算的结果进行存储两种方式优化递归调用

// 直接累加计算
public static long fibonacci1(int n) {
  if(n <= 0 ) return 0;
  if(n == 1) return 1;
  long f2 = 0;
  long f1 = 1;
  long ret = 0;
  for(int i = 2; i <= n; i++) {
    ret = f1 + f2;
    f2 = f1;
    f1 = ret;
  }
  return ret;
}

提示:还可以基于递归的方式使用O(logN)时间复杂度实现,需要

10. 二进制中1的个数

描述:输入一个整数,输出二进制中1的个数。

思路:一个整数的二进制位如果不为0,则至少有1位为1,如果要消除一个整数的二进制位最右边的1,可以使用x & (x-1)实现,这个规则对于负数也适用。

代码:

public static int contOne(int x) {
  int count = 0;
  while(x != 0) {
    count++;
    x = x & (x-1);
  }
  return count;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值