剑指offer刷题笔记(一)
数据结构
-
数组
- 数组【Array】是将相同类型的元素存储于连续内存空间的数据结构,其长度不可变。
- 可变数组【ArrayList】是经常使用的数据结构,其基于数组和扩容机制实现,相比普通数组更加灵活。常用操作有:访问元素、添加元素、删除元素。
-
链表
- 链表以节点为单位,每个元素都是一个独立对象,在内存空间的存储是非连续的。链表的节点对象具有两个成员变量:「值
val
」,「后继节点引用next
」
- 链表以节点为单位,每个元素都是一个独立对象,在内存空间的存储是非连续的。链表的节点对象具有两个成员变量:「值
-
栈
- 栈是一种具有 「先入后出」 特点的抽象数据结构,可使用数组或链表实现
-
队列
- 队列是一种具有 「先入先出」 特点的抽象数据结构,可使用链表实现
非线性数据结构
-
树
- 树是一种非线性数据结构,根据子节点数量可分为 「二叉树」 和 「多叉树」,最顶层的节点称为**「根节点 root」。以二叉树为例,每个节点包含三个成员变量:「值 val」、「左子节点 left」、「右子节点 right」** 。
-
图
- 图是一种非线性数据结构,由「节点(顶点)vertex」和「边 edge」组成,每条边连接一对顶点。根据边的方向有无,图可分为「有向图」和「无向图」。
-
散列表
- 散列表是一种非线性数据结构,通过利用 Hash 函数将指定的「键
key
」映射至对应的「值value
」,以实现高效的元素查找。
- 散列表是一种非线性数据结构,通过利用 Hash 函数将指定的「键
-
堆
- 堆是一种基于「完全二叉树」的数据结构,可使用数组实现。以堆为原理的排序算法称为「堆排序」,基于堆实现的数据结构为「优先队列」。堆分为「大顶堆」和「小顶堆」,大(小)顶堆:任意节点的值不大于(小于)其父节点的值。
题目一:替换空格
请实现一个函数,把字符串 s
中的每个空格替换成"%20"。
示例 1:
输入:s = "We are happy."
输出:"We%20are%20happy."
代码1
class Solution {
public String replaceSpace(String s) {
int length = s.length();
//字符的数组
char[] array = new char[length*3];
int size = 0;
for(int i = 0;i<length;i++){
//取到字符串的每一个字符
//字符串String类中有一个charAt方法来获取字符串中的字符
char c = s.charAt(i);
if(c==' '){
array[size++] = '%';
array[size++] = '2';
array[size++] = '0';
}else{
array[size++]=c;
}
}
String newstr = new String(array,0,size);
return newstr;
}
}
//代码二
class Solution {
public String replaceSpace(String s) {
StringBuilder res = new StringBuilder();
for(Character c:s.toCharArray()){
if(c == ' '){
res.append("%20");
}else{
res.append(c);
}
}
return res.toString();
}
}
//还可以定义一个字符数组,根据空格数定义其长度,然后从后向前进行遍历
其中几个知识点:
- char是表示的是字符(数据类型), String表示的是类,字符串(类可调用方法),使用时要进行相互转化
- String类是final类,也即意味着String类不能被继承,并且它的成员方法都默认为final方法。在Java中,被final修饰的类是不允许被继承的,并且该类中的成员方法都默认为final方法 (创建和运行时刻不同)
- final修饰基本数据类型的变量时,必须赋予初始值且不能被改变,修饰引用变量时,该引用变量不能再指向其他对象
char c = s.charAt(i);
String newstr = new String(array,0,size);
题目二:从尾到头打印链表
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
示例 1:
输入:head = [1,3,2]
输出:[2,3,1]
代码:
// 代码一
class Solution {
public int[] reversePrint(ListNode head) {
ListNode cur = head;
int count = 0;
Stack<Integer> stack = new Stack<>();
while(cur!=null){
stack.push(cur.val);
cur = cur.next;
count++;
}
int[] arr = new int[count];
int i = 0;
while (!stack.empty()){
arr[i++] = stack.pop();
}
return arr;
}
}
//代码二
补充知识点:
- 利用栈限定仅在表尾进行插入和删除。从而实现数据的逆序排列
- 栈的方法
**题目三:**用两个栈实现队列
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
示例 :
输入:
["CQueue","appendTail","deleteHead","deleteHead"]
[[],[3],[],[]]
输出:[null,null,3,-1]
//核心思想:通过两个栈来实现队列,<尾查>在队尾插入元素相当于在一个栈倒入另一个栈,然后在顶部插入新元素,再倒回原来的栈中。
// 在头部删除新元素相当于在在栈顶弹出新元素
class CQueue {
Stack<Integer> stack1;
Stack<Integer> stack2;
int size;
public CQueue() {
stack1 = new Stack<Integer>();
stack2 = new Stack<Integer>();
size = 0;
}
public void appendTail(int value) {
while (!stack1.empty()) {
stack2.push(stack1.pop());
}
stack1.push(value);
while (!stack2.empty()) {
stack1.push(stack2.pop());
}
size++;
}
public int deleteHead() {
if (size == 0){
return -1;
}
size--;
return stack1.pop();
}
}
题目四:表示数值的字符串
//代码一:任务拆解
class Solution {
public boolean isNumber(String s) {
// 1、去除空格
s = s.trim();
// 2、字符串为空直接返回false
if (s.length()==0) return false;
// 3、去除前面的加减号
if (s.charAt(0) == '+' || s.charAt(0) == '-') {
s = s.substring(1); //这部分相当于是从1到末尾
}
// 4、E替换为e
s = s.replace('E', 'e');
// 5、以E为分线分成两个部分
if (s.indexOf('e') > 0) {
int eidx = s.indexOf('e');
String frist = s.substring(0, eidx);
String second = s.substring(eidx+1);
if (second.length()>0){
if (second.charAt(0)=='+'||second.charAt(0)=='-') {
second = second.substring(1);
}
}
// 6、判断第一二部分是不是都是数字
return rightnumber(frist)&&ispurenumber(second);
}
return rightnumber(s);
}
// 7、第一部分可包含小数点 按照小数点进行分成为两个部分,分别判断两部分是不是纯数字
private static boolean rightnumber(String s){
if (s.indexOf('.')>=0){
int index = s.indexOf('.');
String pri = s.substring(0,index);
String last = s.substring(index+1);
// 这里分了三种情况,第一种是前后都不为空
if (pri.length()>0&&last.length()>0){
return ispurenumber(pri)&&ispurenumber(last);
}else if (last.length()>0){
return ispurenumber(last);
}else {
return ispurenumber(pri);
}
}
return ispurenumber(s);
}
// 8、第二部分去除加减号之后是否为纯数字
private static boolean ispurenumber(String s){
if (s.length()==0)return false;
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i)<'0'||s.charAt(i)>'9'){
return false;
}
}
return true;
}
}
class Solution {
public boolean isNumber(String s) {
Map[] states = {
new HashMap<>() {{ put(' ', 0); put('s', 1); put('d', 2); put('.', 4); }}, // 0.
new HashMap<>() {{ put('d', 2); put('.', 4); }}, // 1.
new HashMap<>() {{ put('d', 2); put('.', 3); put('e', 5); put(' ', 8); }}, // 2.
new HashMap<>() {{ put('d', 3); put('e', 5); put(' ', 8); }}, // 3.
new HashMap<>() {{ put('d', 3); }}, // 4.
new HashMap<>() {{ put('s', 6); put('d', 7); }}, // 5.
new HashMap<>() {{ put('d', 7); }}, // 6.
new HashMap<>() {{ put('d', 7); put(' ', 8); }}, // 7.
new HashMap<>() {{ put(' ', 8); }} // 8.
};
int p = 0;
char t;
for(char c : s.toCharArray()) {
if(c >= '0' && c <= '9') t = 'd';
else if(c == '+' || c == '-') t = 's';
else if(c == 'e' || c == 'E') t = 'e';
else if(c == '.' || c == ' ') t = c;
else t = '?';
if(!states[p].containsKey(t)) return false;
p = (int)states[p].get(t);
}
return p == 2 || p == 3 || p == 7 || p == 8;
}
}
题目五:反转列表
// 代码一;双指针循环赋值
public static ListNode reverseList(ListNode head) {
ListNode cur=head, pre = null;
while(cur!=null){
ListNode tem = cur.next;
cur.next = pre;
pre = cur;
cur = tem;
}
return pre;
// 代码二:
class Solution {
public ListNode reverseList(ListNode head) {
return recur(head,null);
}
private ListNode recur(ListNode cur, ListNode pre){
if(cur==null)return pre;
ListNode res = recur(cur.next, cur);
cur.next = pre;
return res;
}
}
题目六:包含 min 函数的栈
public P6(){
st1 = new Stack<>();
st2 = new Stack<>();
}
public void push(int x) {
/*
A.add(x);
if(B.empty() || B.peek() >= x)
B.add(x);
*/
if (st1.empty()) {
st1.push(x);
st2.push(x);
} else {
st1.push(x);
int min = st2.pop();
st2.push(min);
if (min >= x) {
st2.push(x);
} else {
st2.push(min);
}
}
}
public void pop(){
/*
if(A.pop().equals(B.peek()))
B.pop();
*/
st1.pop();
st2.pop();
}
public int min(){
//g
return st2.peek();
}
public int top(){
return st1.peek();
}
}
- 构造方法的名称必须与类同名,并且没有返回值,构造方法主要用于为类的对象定义初始化状态。
题目7:复杂链表的复制
//解法一:借助hashmap来操作
class Solution {
public Node copyRandomList(Node head) {
if (head==null)return null;
Map<Node,Node> map = new HashMap<Node, Node>();
Node cur = head;
//复制各节点,并建立 “原节点 -> 新节点” 的 Map 映射
while (cur!=null){
map.put(cur,new Node(cur.val));
cur = cur.next;
}
cur = head;
//构建新链表的 next 和 random 指向
while (cur!=null){
map.get(cur).next = map.get(cur.next);
map.get(cur).random = map.get(cur.random);
cur = cur.next;
}
return map.get(head);
}
//解法二:借助hashmap的递归操作
Map<Node,Node> map = new HashMap<>();
public Node copyRandomList(Node head) {
if(head == null) return null;
if(map.containsKey(head)) return map.get(head);
Node node = new Node(head.val);
map.put(head,node);
//递归创造出新的节点
node.next = copyRandomList(head.next);
//递归为以上各个节点找到随机指向的节点
node.random = copyRandomList(head.random);
return node;
}
//解法三:暴力穷举
public static Node copyRandomList(Node head) {
if (head==null)return null;
Node cur = head;
Node copycur = new Node(head.val);
Node copyhead = copycur;
cur = cur.next;
while (cur!=null){
copycur.next = new Node(cur.val);
cur = cur.next;
copycur = copycur.next;
}
cur = head;
copycur = copyhead;
Node random;
Node currandom;
Node copycurrandom;
while (cur!=null){
random = cur.random;
if (random == null){
copycur.random = null;
}else {
currandom = head;
copycurrandom = copyhead;
while (currandom!=null){
if (currandom==random){
copycur.random = copycurrandom;
break;
}else{
currandom = currandom.next;
copycurrandom = copycurrandom.next;
}
}
}
cur = cur.next;
copycur = copycur.next;
}
return copyhead;
}
hashmap的使用
- put使用:**hashmap.put(K key,V value)**将键/值对添加到 hashMap 中
- get使用:hashmap.get(Object key) 获取指定 key 对应对 value
题目8:左旋转字符串
public String reverseLeftWords(String s, int n) {
int legth = s.length();
String str1 = (String) s.subSequence(0,n);
//结束索引不包含在内
String str2 = (String) s.subSequence(n,legth);
return str2+str1;
}
题目9:滑动窗口的最大值
//暴力穷举
public static int[] maxSlidingWindow(int[] nums, int k) {
int lens = nums.length;
int [] maxlist = new int[lens-k+1];
if (lens == 0)return new int[0];
for (int i = 0; i < lens-k+1; i++) {
int tem = i;
int max = nums[i];
for (int j = tem; j < tem+k; j++) {
if (nums[j]>max)max = nums[j];
}
maxlist[i] = max;
}
return maxlist;
}
//单调队列的实现方式
public static int[] maxSlidingWindow(int[] nums, int k) {
if (k == 0 || nums.length == 0) return new int[0];
Deque<Integer> deque = new LinkedList<>();
int[] res = new int[nums.length - k + 1];
//未形成窗口
for (int i = 0; i < k; i++) {
while (!deque.isEmpty() && deque.peekLast()<nums[i]) {
deque.removeLast();
}
deque.addLast(nums[i]);
}
res[0] = deque.peekFirst();
//行成窗口
for (int i = k; i < nums.length; i++) {
if (deque.peekFirst() == nums[i - k]) deque.removeFirst();
while (!deque.isEmpty() && deque.peekLast()<nums[i]) {
deque.removeLast();
}
deque.addLast(nums[i]);
res[i - k + 1] = deque.peekFirst();
}
return res;
}
一定要结合min函数的栈来进行理解,巧妙借用队列来解决问题
思维发散:求取某类大集合下面子集合最大最小值等某些特定值的问题都可以借助栈或者对列这些东西来进行辅助操作
Stack是栈的具体实现,Linkedlist是双向对列的具体实现。
题目10: 队列的最大值
class MaxQueue {
Deque<Integer> deq1;
Deque<Integer> deq2;
public MaxQueue() {
deq1 = new LinkedList<>();
deq2 = new LinkedList<>();
}
public int max_value() {
return deq2.isEmpty()? -1:deq2.peekFirst();
}
public void push_back(int value) {
deq1.offer(value);
while (!deq2.isEmpty()&&deq2.peekLast()<value){
deq2.pollLast();
}
deq2.offerLast(value);
}
public int pop_front() {
if (deq1.isEmpty())return -1;
//不要使用
if (deq1.peek().equals(deq2.peekFirst())){
deq2.pollFirst();
}
return deq1.poll();
}
}
填坑记录:==和equals的区别是什么?
01、对于 == 来说:
- 如果比较的是基本数据类型变量,比较两个变量的值是否相等。(不一定数据类型相同)
- 如果比较的是引用数据类型变量,比较两个对象的地址值是否相同,即两个引用是否指向同一个地址值
02、对于 equals 来说:
- 如果类中重写了equals方法,比较内容是否相等。String、Date、File、包装类都重写了Object类的equals方法。
- 如果类中没有重写equals方法,比较地址值是否相等(是否指向同一个地址值)。
- Integer 在常量池中的存储范围为[-128,127],127在这范围内,因此是直接存储于常量池的,而其他不在这范围内,所以会在堆内存
对于这个问题,实现的问题就是在常量池[-128,127]可以使用,所以就是用==就行,但是不在其中就要使用equals!所以统一使用equals
03、常量池
题目11:把字符串转换成整数
// 代码一:
public int strToInt(String str) {
str = str.trim();
if(str.length()==0)return 0;
int sign=1;
int res = 0, max = Integer.MAX_VALUE / 10;
ArrayList<Character> characters = new ArrayList<>();
char a = str.charAt(0);
if (a =='+')sign = 1;
else if (a =='-')sign = -1;
else if (a =='.')sign = 0;
else if (a>='0' && a <= '9')characters.add(a);
else return 0;
for (int i = 1; i < str.length(); i++) {
char c = str.charAt(i);
if (c<'0' || c > '9'){
break;
}else {
characters.add(c);
}
}
for (Character character : characters) {
//求字符对应的数字
int digit =character-'0';
if(res > Integer.MAX_VALUE/10 || res == Integer.MAX_VALUE/10 && digit > Integer.MAX_VALUE%10){
return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
}
res = res*10+digit;
}
return sign * res;
}
数字越界处理:
题目要求返回的数值范围应在 [-2^31, 2^31 - 1][−2 31 ,2 31 −1] ,因此需要考虑数字越界问题。而由于题目指出 环境只能存储 32 位大小的有符号整数 ,因此判断数字越界时,要始终保持 resres 在 int 类型的取值范围内。
e {
characters.add©;
}
}
for (Character character : characters) {
//求字符对应的数字
int digit =character-‘0’;
if(res > Integer.MAX_VALUE/10 || res == Integer.MAX_VALUE/10 && digit > Integer.MAX_VALUE%10){
return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
}
res = res*10+digit;
}
return sign * res;
}
**数字越界处理:**
题目要求返回的数值范围应在 [-2^31, 2^31 - 1][−2 31 ,2 31 −1] ,因此需要考虑数字越界问题。而由于题目指出 环境只能存储 32 位大小的有符号整数 ,因此判断数字越界时,要始终保持 resres 在 int 类型的取值范围内。