本文例子完整源码地址:https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/sword

前一篇《【好书推荐】《剑指Offer》之软技能》中提到了面试中的一些软技能,简历的如何写等。《剑指Offer》在后面的章节中主要是一些编程题并配以讲解。就算不面试,这些题多做也无妨。可惜的是书中是C++实现,我又重新用Java实现了一遍,如果有错误或者更好的解法,欢迎提出交流。

1.赋值运算符函数

  Java不支持赋值运算符重载,略。

2.实现Singleton模式

  饿汉模式

 /**
* 饿汉模式
* @author OKevin
* @date 2019/5/27
**/
public class Singleton { private static Singleton singleton = new Singleton(); private Singleton() { }
public static Singleton getInstance() {
return singleton;
}
}

  优点:线程安全、不易出错、性能较高。

  缺点:在类初始化的时候就实例化了一个单例,占用了内存。

  饱汉模式一

 /**
* 饱汉模式一
* @author OKevin
* @date 2019/5/27
**/
public class Singleton { private static Singleton singleton ; private Singleton() { }
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}

  饱汉模式二

 /**
* 饱汉模式二
* @author OKevin
* @date 2019/5/27
**/
public class Singleton { private static Singleton singleton ; private Singleton() { }
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}

  优点:线程安全,节省内存,在需要时才实例化对象,比在方法上加锁性能要好。

  缺点:由于加锁,性能仍然比不上饿汉模式。

  枚举模式

 /**
* 枚举模式
* @author OKevin
* @date 2019/5/27
**/
public enum Singleton {
INSTANCE; Singleton() { }
}

  在《Effective Java》书中,作者强烈建议通过枚举来实现单例。另外枚举从底层保证了线程安全,这点感兴趣的读者可以深入了解下。尽管枚举方式实现单例看起来比较“另类”,但从多个方面来看,这是最好且最安全的方式。

3.数组中重复的数字

题目:给定一个数组,找出数组中重复的数字。

  解法一:时间复杂度O(n),空间复杂度O(n)

 /**
* 找出数组中重复的数字
* @author OKevin
* @date 2019/5/27
**/
public class Solution { public void findRepeat(Integer[] array) {
Set<Integer> noRepeat = new HashSet<>();
for (Integer number : array) {
if (!noRepeat.contains(number)) {
noRepeat.add(number);
} else {
System.out.println("重复数字:" + number);
}
}
}
}

  *Set底层实现也是一个Map

  通过Map散列结构,可以找到数组中重复的数字,此算法时间复杂度为O(n),空间复杂度为O(n)(需要额外定义一个Map)。

  解法二:时间复杂度O(n^2),空间复杂度O(1)

 /**
* 找出数组中重复的数字
* @author OKevin
* @date 2019/5/27
**/
public class Solution { public void findRepeat(Integer[] array) {
for (int i = 0; i < array.length; i++) {
Integer num = array[i];
for (int j = i + 1; j < array.length; j++) {
if (num.equals(array[j])) {
System.out.println("重复数字:" + array[j]);
}
}
}
}
}

  解法二通过遍历的方式找到重复的数组元素,解法一相比于解法二是典型的“以空间换取时间”的算法

变形:给定一个长度为n的数组,数组中的数字值大小范围在0~n-1,找出数组中重复的数字。

  变形后的题目也可采用上面两种方法,数字值大小范围在0~n-1的特点,不借助额外空间(空间复杂度O(1)),遍历一次(时间复杂度为O(n))的算法

 /**
* 找出数组中重复的数字,数组中的数字值大小范围在0~n-1
* @author OKevin
* @date 2019/5/27
**/
public class Solution {
public void findRepeat(Integer[] array) {
for (int i = 0; i < array.length; i++) {
while (array[i] != i) {
if (array[i].equals(array[array[i]])) {
System.out.println("重复数字:" + array[i]);
break;
}
Integer temp = array[i];
array[i] = array[temp];
array[temp] = temp;
}
}
}
}

  分析:变形后的题目中条件出现了,数组中的值范围在数组长度n-1以内,且最小为0。也就是说,数组中的任意值在作为数组的下标都不会越界,这是一个潜在的条件。根据这个潜在的条件,我们可以把每个值放到对应的数组下标,使得数组下标=数组值。例如:4,2,1,4,3,3。遍历第一个值4,此时下标为0,数组下标≠数组值,比较array[0]与array[4]不相等->交换,4放到了正确的位置上,得到3,2,1,4,4,3。此时第一个值为3,数组下标仍然≠数组值,比较array[0]与array[3]不想等->交换,3放到了正确的位置,得到4,2,1,3,4,3。此时数组下标仍然≠数组值,比较array[0]与array[4]相等,退出当前循环。依次类推,开始数组下标int=1的循环。

4.二维数组中的查找

题目:给定一个二维数组,每一行都按照从左到右依次递增的顺序排序,每一列都按照从上到下依次递增的顺序排序。输入一个二维数组和一个整数,判断该整数是否在二维数组中。

  解法一:遍历n*m大小的二维数组,时间复杂度O(n*m),空间复杂度O(1)

 /**
* 二维数组中查找
* @author OKevin
* @date 2019/5/27
**/
public class Solution { public boolean isExist(Integer[][] twoArray, Integer target) {
for (int i = 0; i < twoArray.length; i++) {
for (int j = 0; j < twoArray[i].length; j++) {
if (twoArray[i][j].equals(target)) {
return true;
}
}
}
return false;
}
}

  优点:简单暴力。

  缺点:性能不是最优的,时间复杂度较高,没有充分利用题目中“有序”的条件。

  解法二:时间复杂度O(n+m),空间复杂度O(1)

 /**
* 二维数组中查找
* @author OKevin
* @date 2019/5/27
**/
public class Solution { public boolean isExist(Integer[][] twoArray, Integer target) {
int x = 0;
int y = twoArray[0].length - 1;
for (int i = 0; i < twoArray.length-1 + twoArray[0].length-1; i++) {
if (twoArray[x][y].equals(target)) {
return true;
}
if (twoArray[x][y] > target) {
y--;
continue;
}
if (twoArray[x][y] < target) {
x++;
}
}
return false;
}
}

  分析:通过举一个实例,找出规律,从右上角开始查找。

  Integer[][] twoArray = new Integer[4][4];

  twoArray[0] = new Integer[]{1, 2, 8, 9};

  twoArray[1] = new Integer[]{2, 4, 9, 12};

  twoArray[2] = new Integer[]{4, 7, 10, 13};

  twoArray[3] = new Integer[]{6, 8, 11, 15};

5.替换空格

题目:将字符串中的空格替换为“20%”。

  解法一:根据Java提供的replaceAll方法直接替换

 /**
* 字符串空格替换
* @author OKevin
* @date 2019/5/28
**/
public class Solution {
public String replaceSpace(String str) {
return str.replaceAll(" ", "20%");
}
}

  这种解法没什么可说。但可以了解一下replaceAll的JDK实现。replaceAll在JDK中的实现是根据正则表达式匹配要替换的字符串。

  解法二:利用空间换时间的方式替换

 /**
* 字符串空格替换
* @author OKevin
* @date 2019/5/28
**/
public class Solution {
public String replaceSpace(String str, String target) {
StringBuilder sb = new StringBuilder();
for (char c : str.toCharArray()) {
if (c == ' ') {
sb.append(target);
continue;
}
sb.append(c);
}
return sb.toString();
}
}

6.从尾到头打印链表

题目:输入一个链表的头节点,从尾到头反过来打印出每个节点的值。

*由于《剑指Offer》采用C++编程语言,这题需要我们先构造出一个节点,模拟出链表的结构。

  定义节点

 /**
* 链表节点定义
* @author OKevin
* @date 2019/5/29
**/
public class Node {
/**
* 指向下一个节点
*/
private Node next;
/**
* 表示节点的值域
*/
private Integer data; public Node(){} public Node(Integer data) {
this.data = data;
}
//省略getter/setter方法
}

  解法一:利用栈先进后出的特点,遍历链表放入栈中,再从栈推出数据

 /**
* 逆向打印链表的值
* @author OKevin
* @date 2019/5/29
**/
public class Solution {
public void tailPrint(Node head) {
Stack<Node> stack = new Stack<>();
while (head != null) {
stack.push(head);
head = head.getNext();
}
while (!stack.empty()) {
System.out.println(stack.pop().getData());
}
}
}

  这种解法“不幸”地借助了额外的空间。

  解法二:既然使用栈的结构,实际上也就可以使用递归的方式逆向打印链表

 /**
* 逆向打印链表的值
* @author OKevin
* @date 2019/5/29
**/
public class Solution {
public void tailPrint(Node head) {
if (head.getNext() != null) {
tailPrint(head.getNext());
}
System.out.println(head.getData());
}
}

  使用递归虽然避免了借助额外的内存空间,但如果链表过长,递归过深易导致调用栈溢出。

  测试程序:

 /**
* @author OKevin
* @date 2019/5/29
**/
public class Main {
/**
* 1->2->3->4->5
* @param args
*/
public static void main(String[] args) {
Node node1 = new Node(1);
Node node2 = new Node(2);
Node node3 = new Node(3);
Node node4 = new Node(4);
Node node5 = new Node(5);
node1.setNext(node2);
node2.setNext(node3);
node3.setNext(node4);
node4.setNext(node5); Node head = node1; Solution solution = new Solution();
solution.tailPrint(head);
}
}

   本文例子完整源码地址:https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/sword

  持续更新,敬请关注公众号:coderbuff,回复关键字“sword”获取相关电子书。

这是一个能给程序员加buff的公众号 (CoderBuff)

【好书推荐】《剑指Offer》之硬技能(编程题1~6)的更多相关文章

  1. 剑指offer(41-45)编程题

    41.入一个递增排序的数组和一个数字S,在数组中查找两个数,是的他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的. class Solution { public: vector&l ...

  2. 剑指offer(36-40)编程题

    两个链表的第一个公共结点 数字在排序数组中出现的次数 二叉树的深度 平衡二叉树 数组中只出现一次的数字 36.输入两个链表,找出它们的第一个公共结点. class Solution1 { public ...

  3. (4)剑指Offer之链表相关编程题

    一 链表中倒数第k个节点 题目描述: 输入一个链表,输出该链表中倒数第k个结点 问题分析: 一句话概括: 两个指针一个指针p1先开始跑,指针p1跑到k-1个节点后,另一个节点p2开始跑,当p1跑到最后 ...

  4. 剑指offer——python【第54题】字符流中第一个不重复的字符

    题目描述 请实现一个函数用来找出字符流中第一个只出现一次的字符.例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g".当从该字符流中读出 ...

  5. 剑指offer——python【第21题】栈的压入、弹出序列

    题目描述 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序.假设压入栈的所有数字均不相等.例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压 ...

  6. 《剑指offer》第十九题(正则表达式匹配)

    // 面试题19:正则表达式匹配 // 题目:请实现一个函数用来匹配包含'.'和'*'的正则表达式.模式中的字符'.' // 表示任意一个字符,而'*'表示它前面的字符可以出现任意次(含0次).在本题 ...

  7. 剑指offer——python【第59题】按之子形顺序打印二叉树

    题目描述 请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推. 解题思路 这道题其实是分层打印二叉树的进阶版 ...

  8. 剑指offer——python【第60题】把二叉树打印成多行

    题目描述 从上到下按层打印二叉树,同一层结点从左至右输出.每一层输出一行.#类似于二维列表[[1,2],[4,5]] 解题思路 其实这倒题和其他类似的题有所区别,这里是分层打印,把每层的节点值放在同一 ...

  9. 剑指offer——python【第39题】平衡二叉树

    题目描述 输入一棵二叉树,判断该二叉树是否是平衡二叉树.   解题思路 平衡二叉树首先是二叉搜索树,且它每个节点的左子树和右子树高度差至多等于1:只要从根节点,依次递归判断每个节点是否满足如上条件即可 ...

  10. 剑指offer——python【第23题】二叉搜索树的后序遍历序列

    题目描述 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果.如果是则输出Yes,否则输出No.假设输入的数组的任意两个数字都互不相同. 解题思路 首先要清楚,这道题不是让你去判断一个给定 ...

随机推荐

  1. leetcode字节跳动专题(持续更新)

    挑战字符串 无重复字符的最长子串 给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度. 示例 1: 输入: "abcabcbb" 输出: 3 解释: 因为无重复字符的最 ...

  2. Beego在views中格式化显示时间(int64转string)

    最近在使用beego开发系统的过程中,遇到时间转换问题,需求如下: 通过以下函数获取Unix时间戳,后台数据库格式为int64: time.Now().Unix() 效果图: 从数据库获取数据,直接以 ...

  3. Day 04 作业

    目录 作业 简述Python的五大数据类型的作用.定义方式.使用方法: 数字类型 字符串类型 列表 字典 布尔型 一行代码实现下述代码实现的功能: 写出两种交换x.y值的方式: 一行代码取出nick的 ...

  4. Day 09 函数

    目录 函数 函数的基本概念 为何使用函数 定义函数 什么是参数(形参,parameter) 定义函数的三种形式 无参函数 有参函数 空函数 函数的参数 形参和实参(parameter & ar ...

  5. 《JavaScript 模式》知识点小抄本(下)

    介绍 最近开始给自己每周订个学习任务,学习结果反馈为一篇文章的输出,做好学习记录. 这一周(02.25-03.03)我定的目标是<JavaScript 模式>的第七章学习一遍,学习结果的反 ...

  6. maven打包mapper.xml打不进去问题

    <resources> <resource> <directory>src/main/java</directory> <includes> ...

  7. 2019年Dubbo你掌握的如何?快看看这30道高频面试题!

    前言 Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案.简单的说,dubbo就是个服务框架,如果没有分布式的需求,其实是不需要用的,只有在分布式 ...

  8. es6 数组去重

    方法一: var arr = [1, 2, 2, 3, 4, 5, 5, 6, 7, 7,8,8,0,8,6,3,4,56,2]; var arr2 = arr.filter((x, index,se ...

  9. SSM项目整合纪实

    一 前 言 本来是为了探究一些功能性问题,需要一套完整的项目架构,本以为SSM用过那么多了,轻松搭建不在话下,但是过程中还是遇到一些问题,踩到一些未曾料想的坑.博文以搭建极简架构为目的,附带一些关键阐 ...

  10. JavaWeb学习——页面跳转方式

    JavaWeb学习——页面跳转方式 摘要:本文主要学习了请求转发和响应重定向,以及两者之间的区别. 请求转发 相关方法 使用HttpServletRequest对象的 getRequestDispat ...