《剑指offer》 要背的代码

目录

 

3--二维数组中的查找

背:

题4:替换空格

背:

String与StringBuffer之间的转换

类似的题目

题目5:从尾到头打印链表

背:ArrayList的用法

ArrayList的源码分析:

扫描二维码关注公众号,回复: 6727680 查看本文章

题6 :重建二叉树

背:三种遍历(前序、中序、后序)的六种实现(递归、循环--栈)、层次遍历

39--二叉树的深度

18--树的子结构

25--二叉树中和为某一值的路径

24--二叉树的后序遍历

宽度优先遍历

23--从上到下遍历二叉树

背:

二叉搜索树

50--树中两个结点的最低公共祖先

27--二叉搜索树与双向链表***

堆与红黑树

30--求最小的k个数字

背代码:

58--二叉树的下一个节点--活页笔记

66--矩阵中的路径--活页笔记

技巧:

67--机器人的运动范围--活页笔记

32--从1到n整数中1出现的次数--纸质考点

数字序列中某一位的数字--活页笔记

背:

33--把数组排成最小的数

背:

把数字翻译成字符串--活页笔记

礼物的最大价值--活页笔记

最长不含重复字符的子字符串--活页笔记

34--丑数

35--(字符串中)第一个只出现一次的字符

55--字符流中第一个不重复(只出现一次)的字符

36--数组中的逆序对*****


3--二维数组中的查找

题目描述

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

思路:

整个二维数组中数字的规律是:从右向左递减、从上到下递增;当寻找目标时,要么从右上角开始,要么从左下角开始

如果目标数比当前数小,向左移动;如果目标数比当前数大,向下移动;如果相等,即找到目标数

注意边界,不要越界了

背:

二维数组的行数:array.length

二维数组的列数:array[0].length

题4:替换空格

题目描述

请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

思路:

方法一:用现成的方法 str.replace(oldstr , newstr);

方法二:O(n)的时间复杂度,(1)先从前往后遍历一遍,得到空格总数;(2)获得需要增加的总量;(3)逐个后移。

public String replaceSpace(StringBuffer str) {
    	String str1 = str.toString();
        str1=str1.replace(" ","%20");
        return str1;
    }

背:

一、String类的常用方法

摘自: https://blog.csdn.net/zhao_yu_lei/article/details/71719504

1.获取:
        1)获取字符串str长度
                int i = str.length();
        2)根据位置(index)获取字符
                char  c = str.charAt(index);
        3)获取字符在字符串中的位置
                int i =str.indexOf(char ch);  //获取的是第一次出现的位置
                int i =str.indexOf(char ch ,int index);  //从位置index后获取ch出现的第一次的位置
                int  i =str.indexOf(str1) ;// 获取str1 在str 第一次出现的位置
                int i=str.indexOf(str1, index0);//获取从index位置后str第一次出现的位置
                int i = str.lastIndexOf(ch或者 str1)  //获取ch或者str1最后出现的位置

2.判断
        1)判断是否以指定字符串str1开头、结尾
                boolean b = str.startWith(str1)  //开头
                boolean b = str.endsWith(str1) //结尾
        2)判断是否包含某一子串
                boolean b = str.contains(str1)
        3)判断字符串是否有内容
                boolean b = str.isEmpty();
        4)忽略大小写判断字符串是否相同
                boolean b = str.equalsIgnoreCase(str1);

3.转换
        1)将字符数组 -char[] ch- 转化成字符串
            i.  String str =new String(ch); //将整个数组变成字符串
            ii. String str =new String(ch,offset,count)
    //将字符数组中的offset位置之后的count个元素转换成字符串  
            1. String str =String.valueOf(ch);
            2. String str =String.copyValueOf(ch,offset,count);
            3. String str =String.copyValueOf(ch);
        2)将字符串转化为字符数组
            char[] ch = str.toCharAarray();
        3)将字节数组转换为字符串
            同上1) 传入类型变为Byte[];
        4)将字符串转换为字节数组
            Byte[] b = str.toByteArray();
        5)将基本数据类型装换成字符串
            String str = String.valueOf(基本数据类型数据);
            若是整形数据可以用 字符串连接符 + "" 
            eg :  String  str = 5+"";
            得到字符串 “5”   

4.替换   replace();
        str.replace(oldchar,newchar)//将str里oldchar变为newchar
        str.replace(str1,str2)//将str中str1,变为str2

5.切割   split();
        String[]  str1 = str.split(","); //将str用 ","分割成String数组

6.子串
        String s = str.substring(begin);
        // s 为 str 从begin位置到最后的字符串
        String s = str.substring(begin,end)
        //s 是 str 从begin 位置到end 位置的字符串

7.转换大小写:
        String s1 = str. toUpperCase(); //将str变成大写字母
        String s2 = str. toLowerCase(); //将str变成小写字母
    除去空格:
        String s =str.trim();
    比较:
        int i = str.compareTo(str1);

二、 StringBuffer常用方法

    /***StringBuffer        是一个容器,长度可变,可以直接操作字符串,用toString方法变为字符串 **/
1.存储
        1)append(); //将指定数据加在容器末尾,返回值也是StringBuffer
        eg:
        StringBuffer sb = new StringBuffer(//可以加str);
        StringBuffer sb1=ab.append(数据) //数据可以任何基本数据类型
    注:此时sb == sb1他们是同一对象,意思是可以不用新建sb1直接 sb.append(数据) 使用时之后接使用sb
2)insert();// 插入
    sb.insert(index ,数据);
2.删除
        sb.delete(start ,end);  //删除start到end的字符内容
//注意:这里的所有包含index的操作都是含头不含尾的
        sb.deleteCharAt(index);//删除指定位置的字符
//清空StringBuffer缓冲区
        sb=new StringBuffer();
        sb.delete(0,sb.length());
3.获取
    char c = sb.charAt(index);//获取index上的字符
    int i = sb.indexOf(char)://获取char字符出现的第一次位置
    //与 String 中的获取方法一致参考前面

4.修改                  String类中无次操作方法
    sb =sb.replace(start,end,string)//将从start开始到end的字符串替换为string;
    sb.setCharAr(index ,char);//将index位置的字符变为新的char

5.反转     sb.reverse();//将sb倒序
6. getChars(int srcBegin,int srcEnd,char[] ch,int chBegin)
//将StringBuffer缓冲区中的指定数据存储到指定数组中

三、StringBuilder
StringBuilder 和 StringBuffer 方法和功能完全一致只是一个是早期版本(StringBuffer)是线程安全的,由于发现利用多线程堆同一String数据操作的情况是很少的,为了提高效率idk1.5以后有StringBuilder 类。意思是多线程操作同一字符串的时候用StringBuffer 安全,现在一般用StringBuilder

String与StringBuffer之间的转换

来源:http://www.oschina.net/code/snippet_2261089_47352

package demo;
/* String与StringBuffer之间的转换
 * String -> StringBuffer
 * 方式一:构造方法
 * 方式二:通过append方法
 * StringBuffer -> String
 * 方式一:通过构造方法
 * 方式二:通过toString方法
 * */
public class StringAndStringBufferSwitch {
    public static void main(String[] args) {
        //String -> StringBuffer
        //创建一个String对象
        String str = "Hi Java!";
        System.out.println(str);
 
        //方式一:构造方法
        StringBuffer buffer = new StringBuffer(str);
        System.out.println(buffer);
 
        //方式二:通过append方法
        StringBuffer buffer2 = new StringBuffer();
        buffer2.append(str);
        System.out.println(buffer2);
 
        //StringBuffer -> String
        //创建一个StringBuffer对象
        StringBuffer buffer3 = new StringBuffer();
        buffer3.append("Happy birthday Java!");
        System.out.println(buffer3);
 
        //方式一:通过构造方法
        String str2 = new String(buffer3); 
        System.out.println(str2);
         
        //方式二:通过toString方法
        String str3 = buffer3.toString();
        System.out.println(str3);
    }
}

类似的题目

有两个排序的数组A1和A2,内存在A1的末尾有足够多的空余空间容纳A2。请实现一个函数,把A2中所有的数字插入到A1中并且所有的数字是排序的。

思路:

从尾到头比较A1和A2中的数字,并把较大的数字复制到A1的合适位置。

题目5:从尾到头打印链表

题目描述

输入一个链表,按链表值从尾到头的顺序返回一个ArrayList。

思路:

方法一:利用特定的函数,插入到链表的第一个位置

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
       ArrayList List = new ArrayList();
        ListNode t = listNode;
        while(t!=null){
            List.add(0,t.val);
            t = t.next;
        }
        return List; 
    }
}

方法二:利用递归

方法三:借助栈

背:ArrayList的用法

ArrayList是动态数组,动态增加和减少元素

初始化:

方式一:
      ArrayList<String> list = new ArrayList<String>();
      String str01 = String("str01");
      String str02 = String("str02");
      list.add(str01);
      list.add(str02);
方式二:
      ArrayList<String> list = new ArrayList<String>(){{add("str01"); add("str02");}};  

其他:


    增加元素到链表中
        boolean add(Element e)
        增加指定元素到链表尾部.
        void add(int index, Element e)
        增加指定元素到链表指定位置.

    从链表中删除元素
        void clear()
        从链表中删除所有元素.
        E remove(int index)
        删除链表中指定位置的元素.
        protected void removeRange(int start, int end)
        删除链表中从某一个位置开始到某一个位置结束的元素。

    获取链表中的元素
        E get(int index)
        获取链表中指定位置处的元素.
        Object[] toArray()
        获取一个数组,数组中所有元素是链表中的元素.(即将链表转换为一个数组)

    修改某个元素
        E set(int index, E element)
        将链表中指定位置上的元素替换成新元素。

    搜索元素
        boolean contains(Object o)
        如果链表包含指定元素,返回true.
        int indexOf(Object o)
        返回元素在链表中第一次出现的位置,如果返回-1,表示链表中没有这个元素。
        int lastIndexOf(Object o)
        返回元素在链表中最后一次出现的位置,如果返回-1,表示链表中没有这个元素。

    检查链表是否为空
        boolean isEmpty()
        返回true表示链表中没有任何元素.

    获取链表大小
        int size()
        返回链表长度(链表包含元素的个数).

ArrayList的源码分析:

https://blog.csdn.net/sihai12345/article/details/79382649

第一遍看得模糊,以后再去看一看

/**
* The size of the ArrayList (the number of elements it contains).
*/
private int size;  // 实际元素个数
transient Object[] elementData; 

上面的 size 是指 elementData 中实际有多少个元素,而 elementData.length 为集合容量,表示最多可以容纳多少个元素

题6 :重建二叉树

题目描述

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

思路:

不含重复的数字 很关键,所以用一个hash来存储:<中序元素,位置>,递归(前序的开始,前序的结束,中序的开始,中序的结束),每次前序的第一个元素就是当前的根结点(建立新节点),再去遍历右子树,再去遍历左子树

易错:随着递归的次数的增加,右子树的前序和中序不是一样的范围。

背:三种遍历(前序、中序、后序)的六种实现(递归、循环--栈)、层次遍历

摘自:https://blog.csdn.net/coder__666/article/details/80349039

前序(先序):

package test;
//前序遍历的递归实现与非递归实现
import java.util.Stack;
public class Test 
{
	public static void main(String[] args)
	{
		TreeNode[] node = new TreeNode[10];//以数组形式生成一棵完全二叉树
		for(int i = 0; i < 10; i++)
		{
			node[i] = new TreeNode(i);
		}
		for(int i = 0; i < 10; i++)
		{
			if(i*2+1 < 10)
				node[i].left = node[i*2+1];
			if(i*2+2 < 10)
				node[i].right = node[i*2+2];
		}
		
		preOrderRe(node[0]);
	}
	
	public static void preOrderRe(TreeNode biTree)
	{//递归实现
		System.out.println(biTree.value);
		TreeNode leftTree = biTree.left;
		if(leftTree != null)
		{
			preOrderRe(leftTree);
		}
		TreeNode rightTree = biTree.right;
		if(rightTree != null)
		{
			preOrderRe(rightTree);
		}
	}
	
	public static void preOrder(TreeNode biTree)
	{//非递归实现
		Stack<TreeNode> stack = new Stack<TreeNode>();
		while(biTree != null || !stack.isEmpty())
		{
			while(biTree != null)
			{
				System.out.println(biTree.value);
				stack.push(biTree);
				biTree = biTree.left;
			}
			if(!stack.isEmpty())
			{
				biTree = stack.pop();
				biTree = biTree.right;
			}
		}
	}
}
 
class TreeNode//节点结构
{
	int value;
	TreeNode left;
	TreeNode right;
	
	TreeNode(int value)
	{
		this.value = value;
	}
}
 
 
 

中序:

public static void midOrderRe(TreeNode biTree)
{//中序遍历递归实现
	if(biTree == null)
		return;
	else
	{
        midOrderRe(biTree.left);
	System.out.println(biTree.value);
	midOrderRe(biTree.right);
	}
}
	
	
public static void midOrder(TreeNode biTree)
{//中序遍历费递归实现
	Stack<TreeNode> stack = new Stack<TreeNode>();
	while(biTree != null || !stack.isEmpty())
	{
		while(biTree != null)
		{
			stack.push(biTree);
			biTree = biTree.left;
		}
		if(!stack.isEmpty())
		{
			biTree = stack.pop();
			System.out.println(biTree.value);
			biTree = biTree.right;
		}
	}
}

后序(难):

算法核心思想:
    首先要搞清楚先序、中序、后序的非递归算法共同之处:用栈来保存先前走过的路径,以便可以在访问完子树后,可以利用栈中的信息,回退到当前节点的双亲节点,进行下一步操作。
    后序遍历的非递归算法是三种顺序中最复杂的,原因在于,后序遍历是先访问左、右子树,再访问根节点,而在非递归算法中,利用栈回退到时,并不知道是从左子树回退到根节点,还是从右子树回退到根节点,如果从左子树回退到根节点,此时就应该去访问右子树,而如果从右子树回退到根节点,此时就应该访问根节点。所以相比前序和后序,必须得在压栈时添加信息,以便在退栈时可以知道是从左子树返回,还是从右子树返回进而决定下一步的操作。
 

public static void postOrderRe(TreeNode biTree)
{//后序遍历递归实现
	if(biTree == null)
		return;
	else
	{
		postOrderRe(biTree.left);
		postOrderRe(biTree.right);
		System.out.println(biTree.value);
	}
}
	
public static void postOrder(TreeNode biTree)
{//后序遍历非递归实现
	int left = 1;//在辅助栈里表示左节点
	int right = 2;//在辅助栈里表示右节点
	Stack<TreeNode> stack = new Stack<TreeNode>();
	Stack<Integer> stack2 = new Stack<Integer>();//辅助栈,用来判断子节点返回父节点时处于左节点还是右节点。
		
	while(biTree != null || !stack.empty())
	{
		while(biTree != null)
		{//将节点压入栈1,并在栈2将节点标记为左节点
			stack.push(biTree);
			stack2.push(left);
			biTree = biTree.left;
		}
			
		while(!stack.empty() && stack2.peek() == right)
		{//如果是从右子节点返回父节点,则任务完成,将两个栈的栈顶弹出
			stack2.pop();
			System.out.println(stack.pop().value);
		}
			
		if(!stack.empty() && stack2.peek() == left)
		{//如果是从左子节点返回父节点,则将标记改为右子节点
			stack2.pop();
			stack2.push(right);
			biTree = stack.peek().right;
		}
				
	}
}

eg:39--二叉树的深度;18--树的子结构;25--二叉树中和为某一值的路径;6--重建二叉树;24--二叉树的后序遍历

39--二叉树的深度

题目描述

输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

public int TreeDepth(TreeNode root) {
     if(root == null) return 0;
     return Math.max(TreeDepth(root.left),TreeDepth(root.right))+1;
}

题目2:输入一棵二叉树的根结点,判断该树是不是平衡二叉树。如果某二叉树中任意结点的左右子树的深度不超过1,那么它就是一棵平衡二叉树。

分析:上面那种解法是需要重复遍历结点的多次的解法,简单但不足以打动面试官

而每个节点只遍历一次的解法,才是面试官喜欢的:

后序遍历,从下至上,最后遍历根结点

18--树的子结构

题目:输入两棵二叉树A和B,判断A和B是不是A的子结构。

分析:分为两步:(1)在树A中找到和B的根结点的值一样的节点R;--遍历树(2)再判断树A中以R为根结点的子树是不是包含和树B一样的结构

注意:一定要检查边界情况,一定要检查树为null的情况

25--二叉树中和为某一值的路径

题目描述

输入一颗二叉树的跟节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)

分析:

深搜 并且借助栈

思路:用前序遍历的方式访问到某一节点时,我们把该节点添加到路径上,并累加该节点的值。如果该节点为叶子节点并且路径中节点之和刚好等于输入的整数,则当前的路径符号要求。如果当前节点不是叶子节点,就继续访问。

当前节点访问结束后,递归函数将自动回到它的父节点。需要在当前路径删除当前节点,并且减去当前节点的值。

这里有个剪枝的问题:如果二叉树中的数字均为正数,则可以剪枝。如果含有负数,必须遍历到最后叶子节点。

24--二叉树的后序遍历

题目描述:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。

思路:最后一个节点是根结点,前面一部分是左子树的节点,中间一部分是右子树的节点,递归的思想

宽度优先遍历

层次遍历

与树的前中后序遍历的DFS思想不同,层次遍历用到的是BFS思想。一般DFS用递归去实现(也可以用栈实现),BFS需要用队列去实现。
层次遍历的步骤是:
    1.对于不为空的结点,先把该结点加入到队列中
    2.从队中拿出结点,如果该结点的左右结点不为空,就分别把左右结点加入到队列中

    3.重复以上操作直到队列为空
 

public static void levelOrder(TreeNode biTree)
{//层次遍历
	if(biTree == null)
		return;
	LinkedList<TreeNode> list = new LinkedList<TreeNode>();
	list.add(biTree);
	TreeNode currentNode;
	while(!list.isEmpty())
	{
		currentNode = list.poll();
		System.out.println(currentNode.value);
		if(currentNode.left != null)
			list.add(currentNode.left);
		if(currentNode.right != null)
			list.add(currentNode.right);
	}
}

eg:23--从上到下遍历二叉树

23--从上到下遍历二叉树

题目描述:从上往下打印出二叉树的每个节点,同层节点从左至右打印。

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    public static ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
        ArrayList<Integer> arrayList = new ArrayList<Integer>();
        if(root==null) return arrayList;
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.add(root);
        while (!queue.isEmpty()){
            if(queue.peek().left!=null) queue.add(queue.peek().left);
            if(queue.peek().right!=null) queue.add(queue.peek().right);
            arrayList.add(queue.peek().val);
            //System.out.println(queue.peek().val);
            queue.poll();
        }
        return arrayList;
    }
}

背:

队列:

Queue<TreeNode> queue = new LinkedList<TreeNode>();

二叉搜索树

eg:50--树中两个结点的最低公共祖先;27--二叉搜索树与双向链表

50--树中两个结点的最低公共祖先

场景1:如果是二叉搜索树,二叉搜索树是排过序的,位于左子树的节点都比父节点小,位于右子树的节点都比父节点大,我们只需要从树的根结点开始和输入的两个数比较。如果当前节点比两个数都大,那么最低的共同父节点一定是在当前节点的左子树中,于是下一步遍历当前节点的左子节点;如果当前节点比两个数都小,那么最低的共同父节点一定是在当前节点的右子树中,于是下一步遍历当前节点的右子节点。这样在树中从上到下找到的第一个在两个输入节点的值之间的节点,就是最低公共祖先。

场景2:不是二叉搜索树,甚至都不是二叉树,只是普通的树,但是节点中有指向父节点的指针

分析:转换成求两个链表的第一个公共节点

场景3:是普通的树,而且没有指向父节点的指针

分析:

分析:这样的话,需要遍历两遍树,每遍历一次时间复杂度是O(n),得到的路径长度最差是O(n),通常情况下是O(logn)

27--二叉搜索树与双向链表***

题目:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整

思路:

递归的思想,10左链接的是其左子树的(变成双向链表后的)最后一个元素,右链接的是其右子树变成双向链表后的第一个元素

最后返回整个链表的最左边的元素

//1、如果左子树不为空,就把左子树变成双向链表,返回原来左子树的根
            //2、寻找左子树的最后一个节点  
            //3、把左子树的最后一个节点和根链接起来
//4、如果右子树不为空,就把右子树变成双向链表
            //5、寻找右子树的第一个节点
            //6、把右子树的第一个节点和根结点链接起来

//返回该根结点

63--二叉搜索树的第k个节点

活页笔记

题目描述:给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8)    中,按结点数值大小顺序第三小结点的值为4。

分析:二叉树的中序遍历是一个递增的顺序。题目可以转换为:二叉搜索树的中序排序的第k个节点。

思路:中序遍历,把k传下来,每遍历到一个点,就k--,直到k=0的点就是我们要找的点

时间复杂度是O(n),然后可以优化一下,就是:(1)当k==0,就是我们要找的点;(2)当k>0时,再遍历右子树。注意这里不能是k!=0,因为k有可能会小于0。

public class Solution {
    TreeNode res = null;
    int i = 0;
    TreeNode KthNode(TreeNode pRoot, int k)
    {
        if(pRoot == null) return null;
        res = KthNode(pRoot.left,k);
        i++;//本来这里是用k--,来判断,但是这里是值传递,就设置了一个全局变量
        if(i==k) res = pRoot;
        if(i<k) res = KthNode(pRoot.right,k);
        return res;
    }
}

堆与红黑树

eg:30--求最小的k个数字

30--求最小的k个数字

自己写的博客:

https://blog.csdn.net/qq_39474604/article/details/91360500

题目描述:输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。

分析:堆排序--大顶堆(维持在4个数字),如果当前元素比堆顶元素小,就删除堆顶元素,并且将当前元素入堆

背代码:

import java.util.Comparator;
import java.util.ArrayList;
import java.util.PriorityQueue;

public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        //存放最后的结果的k个数字
        ArrayList<Integer> res = new ArrayList<Integer>();
        //边界问题
        int length = input.length;
        if(k>length || k== 0) return res;
        //利用优先级队列(基于堆实现的)
        PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(k , new Comparator<Integer>(){
            @Override
            public int compare(Integer o1 , Integer o2){
                return o2.compareTo(o1);//如果o2>o1 返回1
            }
        });
        for(int i = 0 ;i<length ;i++){
            if(maxHeap.size() != k){
                maxHeap.offer(input[i]);
            }else if(maxHeap.peek() > input[i]){
                Integer temp = maxHeap.poll();
                temp = null;
                maxHeap.offer(input[i]);
            }
        }
        for(Integer integer : maxHeap){
            res.add(integer);
        }
        return res;
    }
}

58--二叉树的下一个节点--活页笔记

超级经典的问题,分情况讨论

66--矩阵中的路径--活页笔记

先看一下活页笔记,再看代码:里面有注释的思路

题目描述:请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则之后不能再次进入这个格子。 例如 a b c e s f c s a d e e 这样的3 X 4 矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。

https://www.nowcoder.com/practice/c61c6999eecb4b8f88a98f66b273a3cc?tpId=13&tqId=11218&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking

技巧:

//用四个数组来表示,就不用写if
int [] dx = {-1,0,1,0}; //上(x-1)右(x不变)下(x—+1)左(x不变)
int [] dy = {0,1,0,-1};

67--机器人的运动范围--活页笔记

题目描述:地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?

思路:代码中有,用的是宽度优先遍历,时间复杂度为O(n)

32--从1到n整数中1出现的次数--纸质考点

我的博客:

https://blog.csdn.net/qq_39474604/article/details/91374967

数字序列中某一位的数字--活页笔记

背:

java计算中只有向下取整:向上取整(n/i)= 向下取整 (n+i-1)/i  )

题目:https://www.acwing.com/problem/2/

33--把数组排成最小的数

题目描述:输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

思路:所有数排序,排序时用自己定义的排序方式。

import java.util.ArrayList;

public class Solution {
    //首先定义一个比较运算
    public static boolean cmp(int a ,int b){
        String as = String.valueOf(a);
        String bs = String.valueOf(b);
        int res = (as+bs).compareTo(bs+as);
        //as+bs > bs+as  res>0
        //as+bs > bs+as  res=0
        //as+bs < bs+as  res<0
        if(res > 0) return true;
        else return false;
    }

    public static void bubbleSort(int [] a){
        int temp;
        for(int i = 1;i<a.length;i++){
            for(int j = 0;j<a.length-i;j++){
                if(cmp(a[j] ,a[j+1])){
                    temp = a[j];
                    a[j] = a[j+1];
                    a[j+1] = temp;
                }
            }
        }
    }
    
    public static String PrintMinNumber(int [] numbers) {
        bubbleSort(numbers);
        String res = "";
        for(int j = 0;j<numbers.length;j++){
            res += numbers[j];
        }
        return res;
    }
}

背:

冒泡排序、字符串的比较、String和int的转换

把数字翻译成字符串--活页笔记

题目+代码:https://www.acwing.com/problem/content/55/

给定一个数字,我们按照如下规则把它翻译为字符串:

0翻译成”a”,1翻译成”b”,……,11翻译成”l”,……,25翻译成”z”。

一个数字可能有多个翻译。例如12258有5种不同的翻译,它们分别是”bccfi”、”bwfi”、”bczi”、”mcfi”和”mzi”。

请编程实现一个函数用来计算一个数字有多少种不同的翻译方法。

样例

输入:"12258"

输出:5

礼物的最大价值--活页笔记

题目+代码:https://www.acwing.com/problem/content/56/

题目描述:

在一个m×n的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于0)。

你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格直到到达棋盘的右下角。

给定一个棋盘及其上面的礼物,请计算你最多能拿到多少价值的礼物?

注意:

  • m,n>0

样例:

输入:
[
  [2,3,1],
  [1,7,1],
  [4,6,1]
]

输出:19

解释:沿着路径 2→3→7→6→1 可以得到拿到最大价值礼物。

分析:经典的DP问题

最长不含重复字符的子字符串--活页笔记

题目+代码:https://www.acwing.com/problem/content/57/

题目描述:

请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。

假设字符串中只包含从’a’到’z’的字符。

样例

输入:"abcabc"

输出:3

分析:双指针算法的经典问题,用hash表(int[26]),O(n)

34--丑数

题目描述:把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

分析:本题其实是三路归并,《剑指offer》P185 + 活页笔记

关键代码:

while(res_i<index){
    int t = Math.min(res[i]*2,Math.min(res[j]*3,res[k]*5));
    res[res_i++] = t;
    if(res[i]*2 == t) i++;
    if(res[j]*3 == t) j++;
    if(res[k]*5 == t) k++;
}

35--(字符串中)第一个只出现一次的字符

题目描述:在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).

分析:ASCII码有127个,0~31是控制字符如换行回车删除等,32-126是打印字符,可以通过键盘输入并且能够显示出来。

用哈希的思想,时间复杂度为O(n+127)

另外《剑指offer》上面说:字符是一个长度为8的数据类型,因此总共有256种可能。于是创建一个长度为256的数组,每个字母根据其ASCII码值作为数组的下边对应数组的一个数字,而数组中存储的是每个字符出现的次数。这样就创建了一个大小为256,以字符ASCII码为键值的hash表。

相关题目《剑指offer》P188:

1、定义一个函数,输入两个字符串,从第一个字符串中删除在第二个字符串中出现过的所有字符。

解决:创建一个用数组实现的简单哈希来存储第二个字符串。

2、定义一个函数,删除字符串中所有重复出现的字符。

解决:布尔型数组(初值为false),第一次出现,置位true

3、在英语中,如果两个单词中出现的字母相同,并且每个字母出现的次数也相同,那么这两个单词互为变位词。

解决:用一个hash表,扫描第一个单词的时候,对应位置加1,扫描第二个单词的时候,对应位置减1。如果扫描完之后,哈希表中所有位置均为0 ,那么这两个字符串互为变位词。

55--字符流中第一个不重复(只出现一次)的字符

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

输出描述:如果当前字符流没有存在出现一次的字符,返回#字符。

分析:活页笔记

1、双指针算法,

2、单调队列算法:节省空间,可以把多余的字母删掉,不会占着空间

定义两个数据结构:哈希表:用来存储每个字符出现的次数;队列:控制台的输入

import java.util.*;
public class Solution {
    Map<Character, Integer> map = new HashMap<Character, Integer>();
    Queue<Character> queue = new LinkedList<Character>();
    //Insert one char from stringstream
    public void Insert(char ch)
    {
        if(map.containsKey(ch)){
            //已经存在过,就不插入队列中,并且把队列头中count>1的删除(错误:与该字符相等的)都删除
            int count = map.get(ch)+1;
            map.put(ch,count);
            while((!queue.isEmpty()) && map.get(queue.peek())>1){//****
                queue.poll();
            } 
        }else{
            //没有存在过,就加入map,加入队尾
            map.put(ch,1);
            queue.offer(ch);
        }
    }
    //return the first appearence once char in current stringstream
    public char FirstAppearingOnce()
    {
        if(queue.isEmpty()) return '#';
        return queue.peek();
    }

}

36--数组中的逆序对*****

分析:经典题、模板题

题目描述:在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007

输入描述:题目保证输入的数组中没有的相同的数字

数据范围:

对于%50的数据,size<=10^4

对于%75的数据,size<=10^5

对于%100的数据,size<=2*10^5

示例1

输入

1,2,3,4,5,6,7,0

输出

7

分析:最暴力的做法 O(n*n),只能通过50%的样例

归并排序的时间复杂度是O(n*logn),用归并排序的思想来解决这个问题,活页笔记

public class Solution {
    public int merge(int [] array , int l ,int r, int [] temp){
        if(l>=r) return 0;
        int mid = (l+r)>>1;
        int res = merge(array,l,mid , temp) + merge(array , mid + 1,r, temp);
        if(res>=1000000007)//数值过大求余
        {
            res%=1000000007;
        }
        int i = l,j = mid+1;
        int t = 0;
        while(i<=mid && j<=r){
            if(array[i]<=array[j]){
                temp[t++] = array[i];
                i++;
            }else{
                temp[t++] = array[j];
                res += mid-i+1;
                if(res>=1000000007)//数值过大求余
                {
                    res%=1000000007;
                }
                j++;
            }
        }
        while(i<=mid) temp[t++] = array[i++];
        while(j<=r) temp[t++] = array[j++];
        i = l; t = 0;
        while(i<=r) array[i++] = temp[t++];
        return res;
    }
    public int InversePairs(int [] array) {
        if(null == array || array.length == 0) return 0;
        int [] temp = new int [array.length];
        int res = merge(array , 0 , array.length-1,temp)%1000000007;
        return res;
    }
}

37--两个链表的第一个公共结点

题目描述:输入两个链表,找出它们的第一个公共结点。活页笔记

思路1:蛮力 O(m*n)

思路2:借助栈,分别把两个链表的节点放入到两个栈中,这样两个链表的尾节点就位于两个栈的栈顶,接下来比较两个栈顶的节点是否相同。如果相同,则把栈顶弹出接着比较下一个栈顶,直到找到最后一个相同的节点。

时间复杂度O(m+n),空间复杂度也是O(m+n)

思路3:还有一个更简单的办法,首先遍历两个链表得到它们的长度,就能知道哪个链表比较长了,以及长的链表比短的链表多几个节点。第二次遍历的时候,在较长的链表上先走若干步,接着再同时在两个链表上遍历,找到第一个相同的节点就是它们的第一个公共节点。

思路4:两个指针:P,Q;P从第一个链表开始走,走到尾部,从第二个链表开始走;Q从第二个链表开始走,走到尾部,从第一个链表开始走。

总共有两种情况,PQ在走了相同的步数之后,指向同一个地方,可能是某结点,也可能是null

时间复杂度O(2*n)

public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        ListNode p = pHead1;
        ListNode q = pHead2;
        while(p!=q){
            if(p!=null) p = p.next;//错误:p = p->next
            else p = pHead2;
            if(q!=null) q = q.next;
            else q = pHead1;
        }
        return p;
    }
}

相关题目:

如果把图5.3逆时针旋转90度,我们就会发现两个链表的拓扑形状和一个树的形状非常相似,只是这里的指针是从叶节点指向根节点的。两个链表的第一个公共节点正好就是二叉树中两个叶节点的最低公共祖先。

38--数字在排序数组中出现的次数

题目:统计一个数字在排序数组中出现的次数。例如输入排序数组{1,2,3,3,3,3,4,5}和数字3,由于3在这个数组中出现了4次,因此输出4

思路1:如果用哈希,时间复杂度是O(n),效率低

思路2:二分 O(logn)

背:整数二分的终极模板--不咋懂,背下来吧

注意:算术右移 而非 除法,算术右移的结果始终是向下取整,而除法是向0取整,使用算术右移可以使得区间为负数时也可以正常二分,若我们能保证在非负整数集合上2分,除2也是可以的

接着再去看本题目:

public class Solution {
    public int GetNumberOfK(int [] array , int k) {
        if(null == array || array.length==0) return 0;
        int l = 0, r = array.length-1;
        while(l<r){
            int mid = (l+r) >>1;
            if(array[mid]<k) l = mid+1;
            else r = mid;
        }
        if(array[l]!=k) return 0;
        int left = l;
        
        l = 0;
        r = array.length-1;
        while(l<r){
            int mid = (l+r+1) >>1;
            if(array[mid]<=k) l = mid;
            else r = mid-1;
        }
        return r-left+1;
    }
}

0~n-1中缺失的数字--活页笔记

https://www.acwing.com/problem/content/64/

题目:一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0到n-1之内。

在范围0到n-1的n个数字中有且只有一个数字不在该数组中,请找出这个数字。

样例

输入:[0,1,2,4]

输出:3

分析:思路1:nlogn 排序

思路2:O(n) 基数排序

思路3:0~n-1求和(等差数列,高斯公式)

思路4: a^b^a = b

40--数组中只出现一次的两个数字

活页笔记

题目描述:一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

分析:若一个数组中,有一个数出现了一次,其他数都出现了两次,可以用异或

本题中,也是用异或:........x^y = x^y != 0

x^y 一定有一位为1 , 找出这一位,根据这一位是否为1,将数组分为两部分

时间复杂度:O(n)

//num1,num2分别为长度为1的数组。传出参数
//将num1[0],num2[0]设置为返回结果
public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        //先求所有数的异或和
        int sum = 0;
        for(int i = 0;i < array.length;i++) sum ^= array[i];
        //找sum中第k位:即不为零的位置
        int k = 0;
        while(((sum>>k)&1) == 0) k++;//注意这里要都加上括号,以防错误
        //第一个集合
        num1[0] = 0;
        for(int i = 0;i<array.length;i++){
            if(((array[i]>>k)&1) == 1) num1[0] ^= array[i];
        }
        num2[0] = sum ^ num1[0];
    }
}

数组中唯一只出现一次的数字--活页笔记*****

https://www.acwing.com/problem/content/70/

题目:

在一个数组中除了一个数字只出现一次之外,其他数字都出现了三次

请找出那个只出现一次的数字。

你可以假设满足条件的数字一存在。

思考题:

  • 如果要求只使用 O(n)的时间和额外 O(1)的空间,该怎么做呢?

样例

输入:[1,1,1,2,2,2,3,4,4,4]

输出:3

方法1:(位运算) O(n2)

基于位运算,若一个数字出现三次,那么它的二进制表示中的每一位也出现三次。把数组中的所有出现三次的数字的二进制表示的每一位分别加起来,则每一位的和都能被3整除,再把所求的那个数字的每一位分别加上去,若二进制对应位的和还能被3整除,则所求数字对应位为0,否则为1。最终,得到所求数字的二进制表示

方法2:状态机

(暴力枚举) O(n)

状态机连续遇到n个1以后变为0,遇到0直接变为0

数组中数值和下标相等的元素--活页笔记

https://www.acwing.com/problem/content/65/

题目:假设一个单调递增的数组里的每个元素都是整数并且是唯一的。

请编程实现一个函数找出数组中任意一个数值等于其下标的元素。

例如,在数组[-3, -1, 1, 3, 5]中,数字3和它的下标相等。

样例

输入:[-3, -1, 1, 3, 5]

输出:3

注意:如果不存在,则返回-1。

方法1:最暴力的做法,从前往后遍历一遍,O(n)

方法2:{ nums[i] }具有单调性,{ nums[i]-i }也是具有单调性(证明过程看笔记),然后二分法,即从一个单调的数组里找一个元素为0的数。

具体实现伪代码看笔记

41--和为S的两个数

活页笔记

题目描述:输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。

输出描述: 对应每个测试案例,输出两个数,小的先输出。

思路1:最暴力的做法,就是两层循环,O(n*n)

思路2:用一个HashSet(不需要用HashMap),记录一下这个数有没有出现过,O(n)

import java.util.ArrayList;
import java.util.HashSet;//这个要加上
public class Solution {
    public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
        ArrayList<Integer> arrayList = new ArrayList<Integer>();
        HashSet hashSet = new HashSet();
        int min = 0;
        int max = 0;
        boolean zhaodao = false;
        int ji = Integer.MAX_VALUE;
        for(int i = 0;i<array.length;i++){
            if(hashSet.contains(sum-array[i])){
                zhaodao = true;
                int t_ji = (sum-array[i])*array[i];
                if(t_ji<ji){//找乘积最小的
                    min = sum-array[i];
                    max = array[i];
                }
            }
            hashSet.add(array[i]);
        }
        if(zhaodao){
            arrayList.add(min);
            arrayList.add(max);
        }
        return arrayList;
        
    }
}

41--和为S的连续正数序列

活页笔记

题目描述:小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!

输出描述:  输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序

分析:S=15的情况有:1+2+3+4+5 ;4+5+6; 7+8;

思路1:最暴力的做法,枚举,先枚举起始数(i) 是多少,再枚举终止数(j)是多少,再算i~j的和(等差数列求和)是否等于S

时间复杂度是O(n*n)

思路2:将上面那种思路进行优化,枚举出来i后,j是可以二分出来的,因为j越大总和越大,j越小总和越小

时间复杂度是O(n*logn)

思路3:每一个合法的i,都有一个j与之对应。那么随着i增大,j有单调性,也是增大的。那么就可以用双指针做法,O(n)

因为题目中是正整数序列,所以才有单调性,如果含有负数,就没有单调性了

import java.util.ArrayList;
public class Solution {
    public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
        ArrayList<ArrayList<Integer>> array_res = new ArrayList<ArrayList<Integer>>();
        for(int i = 1,j = 1, s = 1; i<=sum;i++){
            while(s<sum) s += ++j;//找到满足当前i的j
            if(s==sum && (j-i)>=1){//(j-i)>=1 这一截一定要加上
                ArrayList<Integer> array_temp = new ArrayList();
                for(int k = i;k<=j;k++) array_temp.add(k);
                array_res.add(array_temp);
            }
            s -= i;
        }
        return array_res;
    }
}

接下来两道题是跟翻转序列有关的

42--翻转单词顺序

活页笔记

题目描述:牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?

要求:时间O(n) , 空间O(1)

思路:第一步:直接翻转整个句子,用两个指针,一个从0开始,一个从最后一个开始

第二步:翻转每个单词

类似的题:一个矩阵,翻转90度,不好做,可以把它变成两次翻转的操作

public class Solution {
    public static String ReverseSentence(String str) {
        StringBuilder strBuilder = new StringBuilder(str);
        if(strBuilder.length()==0 || strBuilder.length()==1) return str;
        //整个字符串翻转
        for(int i =0 , j = strBuilder.length()-1;i<j;i++,j--){
            char ch = strBuilder.charAt(i);
            strBuilder.setCharAt(i,strBuilder.charAt(j)) ;
            strBuilder.setCharAt(j,ch);
        }
        //把每个单词翻转
        for(int i = 0; i<strBuilder.length();){
            int j = i;
            while(j<strBuilder.length() && strBuilder.charAt(j)!=' ') j++;
            System.out.println(i);
            System.out.println(j);
            for(int p = i ,q = j-1 ; p < q ; p++ ,q--){
                char ch = strBuilder.charAt(p);
                strBuilder.setCharAt(p,strBuilder.charAt(q)) ;//一开始总是错,是因为这里面写的是str而不是strBuilder
                strBuilder.setCharAt(q,ch);
            }
            i = j+1;
            //break;
        }
        str=strBuilder.toString();
        return str;
    }
}

StringBuilder

42--左旋字符串

题目描述:汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!

思路1:一般做法,空间O(n)

思路2:时间O(n) ,空间O(1)

和刚刚的那道题目类似,先整体换一下,再两部分翻转

abcdefg

gfedcba    第一步:先整体换一下

cdefgab    第二步:再两部分翻转

public class Solution {
    public String LeftRotateString(String str,int n) {
        StringBuilder strBuilder = new StringBuilder(str);
        if(strBuilder.length()==0 || strBuilder.length()==1) return str;
        //整个字符串翻转
        for(int i =0 , j = strBuilder.length()-1;i<j;i++,j--){
            char ch = strBuilder.charAt(i);
            strBuilder.setCharAt(i,strBuilder.charAt(j)) ;
            strBuilder.setCharAt(j,ch);
        }
        //再把两部分翻转
        for(int i =0 , j = strBuilder.length()-n-1;i<j;i++,j--){
            char ch = strBuilder.charAt(i);
            strBuilder.setCharAt(i,strBuilder.charAt(j)) ;
            strBuilder.setCharAt(j,ch);
        }
        for(int i =strBuilder.length()-n, j = strBuilder.length()-1;i<j;i++,j--){
            char ch = strBuilder.charAt(i);
            strBuilder.setCharAt(i,strBuilder.charAt(j)) ;
            strBuilder.setCharAt(j,ch);
        }
        
        str=strBuilder.toString();
        return str;
    }
    
}

65--滑动窗口的最大值*****

非常经典的单调队列的问题

关键: 应该在队列里存入数字在数组里的下标

题目描述: 给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

import java.util.*;
public class Solution {
    public ArrayList<Integer> maxInWindows(int [] num, int size)
    {
        ArrayList<Integer> res = new ArrayList<Integer>();
        //边界
        if(num==null || num.length<size || size<=0 ) return res;
        //双端队列
        Deque<Integer> deque = new LinkedList<Integer>();

        //先找到第一个
        for(int i = 0; i<num.length ;i++){
            //如果队头有元素,先判断队头的元素是否已经过期
            if(!deque.isEmpty()){
                int tou_i = deque.getFirst();
                if(i-tou_i>=size) deque.pollFirst();
            }
            
            //如果队尾有元素,如果队尾的元素小于该元素,就删除队尾的元素,再插入该元素
            if(!deque.isEmpty()){
                int wei_i = deque.getLast();
                if(num[wei_i]<=num[i]) deque.pollLast();
            }
            deque.addLast(i);
            //取出队头,不会删除
            //再判断队头的元素是否需要删除
            int tou_i = deque.getFirst();
            while(num[tou_i] < num[i]){
                deque.pollFirst();
                tou_i = deque.getFirst();
            } 
            if(i>=(size-1)) res.add(num[deque.getFirst()]);
        }
        return res;
    }
}

几道滑动窗口有关的面试题

摘自:https://mp.weixin.qq.com/s/_8durJjEK-joCLNa58auOQ

0)何为滑动窗口算法

滑动问题包含一个滑动窗口,它是一个运行在一个大数组上的子列表,该数组是一个底层元素集合。

假设有数组 [a b c d e f g h ],一个大小为 3 的 滑动窗口 在其上滑动,则有:

[a b c]
  [b c d]
    [c d e]
      [d e f]
        [e f g]
          [f g h]

一般情况下就是使用这个窗口在数组的 合法区间 内进行滑动,同时 动态地 记录一些有用的数据,很多情况下,能够极大地提高算法地效率。

1) 滑动窗口最大值

题目来源于 LeetCode 上第 239 号问题:滑动窗口最大值。题目难度为 Hard,目前通过率为 40.5% 。

题目描述:给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口 k 内的数字。滑动窗口每次只向右移动一位。

返回滑动窗口最大值。

示例:

输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7]
解释:

  滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

题目解析

利用一个 双端队列,在队列中存储元素在数组中的位置, 并且维持队列的严格递减,,也就说维持队首元素是 最大的 ,当遍历到一个新元素时, 如果队列里有比当前元素小的,就将其移除队列,以保证队列的递减。当队列元素位置之差大于 k,就将队首元素移除。

什么是双端队列(Dqueue)

Deque 的含义是 “double ended queue”,即双端队列,它具有队列和栈的性质的数据结构。顾名思义,它是一种前端与后端都支持插入和删除操作的队列。

Deque 继承自 Queue(队列),它的直接实现有 ArrayDeque、LinkedList 等。

class Solution {
   public int[] maxSlidingWindow(int[] nums, int k) {
        //有点坑,题目里都说了数组不为空,且 k > 0。但是看了一下,测试用例里面还是有nums = [], k = 0,所以只好加上这个判断
        if (nums == null || nums.length < k || k == 0) return new int[0];
        int[] res = new int[nums.length - k + 1];
        //双端队列
        Deque<Integer> deque = new LinkedList<>();
        for (int i = 0; i < nums.length; i++) {
            //在尾部添加元素,并保证左边元素都比尾部大
            while (!deque.isEmpty() && nums[deque.getLast()] < nums[i]) {
                deque.removeLast();
            }
            deque.addLast(i);
            //在头部移除元素
            if (deque.getFirst() == i - k) {
                deque.removeFirst();
            }
            //输出结果
            if (i >= k - 1) {
                res[i - k + 1] = nums[deque.getFirst()];
            }
        }
        return res;
     }
}

2) 无重复字符的最长子串

题目来源于 LeetCode 上第 3 号问题:无重复字符的最长子串。题目难度为 Medium,目前通过率为 29.0% 。

题目描述

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

输入: "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

题目解析:

建立一个256位大小的整型数组 freg ,用来建立字符和其出现位置之间的映射。

维护一个滑动窗口,窗口内的都是没有重复的字符,去尽可能的扩大窗口的大小,窗口不停的向右滑动。

  1. 如果当前遍历到的字符从未出现过,那么直接扩大右边界;

  2. 如果当前遍历到的字符出现过,则缩小窗口(左边索引向右移动),然后继续观察当前遍历到的字符;

  3. 重复(1)(2),直到左边索引无法再移动;

  4. 维护一个结果res,每次用出现过的窗口大小来更新结果 res,最后返回 res 获取结果。

// 滑动窗口
// 时间复杂度: O(len(s))
// 空间复杂度: O(len(charset))
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int freq[256] = {0};
        int l = 0, r = -1; //滑动窗口为s[l...r]
        int res = 0;
        // 整个循环从 l == 0; r == -1 这个空窗口开始
        // 到l == s.size(); r == s.size()-1 这个空窗口截止
        // 在每次循环里逐渐改变窗口, 维护freq, 并记录当前窗口中是否找到了一个新的最优值
        while(l < s.size()){
            if(r + 1 < s.size() && freq[s[r+1]] == 0){
                r++;
                freq[s[r]]++;
            }else {   //r已经到头 || freq[s[r+1]] == 1
                freq[s[l]]--;
                l++;
            }
            res = max(res, r-l+1);
        }
        return res;
    }
};

3) 存在重复元素 II

题目来源于 LeetCode 上第 219 号问题:存在重复元素 II。题目难度为 Easy,目前通过率为 33.9% 。

题目描述

给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的绝对值最大为 k

示例 1:

输入: nums = [1,2,3,1], k = 3
输出: true

示例 2:

输入: nums = [1,0,1,1], k = 1
输出: true

示例 3:

输入: nums = [1,2,3,1,2,3], k = 2
输出: false

题目解析

使用用滑动窗口与查找表来解决。

  • 设置查找表record,用来保存每次遍历时插入的元素,record的最大长度为k
  • 遍历数组nums,每次遍历的时候在record查找是否存在相同的元素,如果存在则返回true,遍历结束
  • 如果此次遍历在record未查找到,则将该元素插入到record中,而后查看record的长度是否为k + 1

  • 如果此时record的长度是否为k + 1,则删减record的元素,该元素的值为nums[i - k]

  • 如果遍历完整个数组nums未查找到则返回false

动画展示:https://mp.weixin.qq.com/s?__biz=MzU0OTk3ODQ3Ng==&mid=2247485110&idx=1&sn=f6851fcd0d6406772d3a46f9a1e0a621&chksm=fba6eeb5ccd167a38a9601edb83231024a8003c82d7c4074e27949f7b72750309114ac80de9c&mpshare=1&scene=24&srcid=0611Bn28jDo13AZB9ItON5Fd#rd

// 时间复杂度: O(n)
// 空间复杂度: O(k)
class Solution {
public:
    bool containsNearbyDuplicate(vector<int>& nums, int k) {
        if(nums.size() <= 1)  return false;
        if(k <= 0)  return false;
        unordered_set<int> record;
        for(int i = 0 ; i < nums.size() ; i ++){
            if(record.find(nums[i]) != record.end()){
                return true;
            }
            record.insert(nums[i]);
            // 保持record中最多有k个元素
            // 因为在下一次循环中会添加一个新元素,使得总共考虑k+1个元素
            if(record.size() == k + 1){
                record.erase(nums[i - k]);
            }
        }
        return false;
    }
};

4)长度最小的子数组

题目来源于 LeetCode 上第 209 号问题:长度最小的子数组。题目难度为 Medium,目前通过率为 37.7% 。

题目描述

给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组如果不存在符合条件的连续子数组,返回 0。

示例:

输入: s = 7, nums = [2,3,1,2,4,3]
输出: 2
解释: 子数组 [4,3] 是该条件下的长度最小的连续子数组。

题目解析

定义两个指针 left 和 right ,分别记录子数组的左右的边界位置。

  1. right 向右移,直到子数组和大于等于给定值或者 right 达到数组末尾;

  2. 更新最短距离,将 left 像右移一位, sum 减去移去的值

  3. 重复(1)(2)步骤,直到 right 到达末尾,且 left 到达临界位置

设置滑动窗口的长度为 0 ,位于数轴的最左端。

1. 滑动窗口右端 R 开始移动,直到区间满足给定的条件,也就是和大于 7 ,此时停止于第三个元素 2,当前的最优长度为 4

2. 滑动窗口左端 L 开始移动,缩小滑动窗口的大小,停止于第一个元素 3,此时区间和为 6,使得区间和不满足给定的条件(此时不大于 7)

3. 滑动窗口右端 R 继续移动,停止于第四个元素 4,此时和位 10 ,但最优长度仍然为 4

// 滑动窗口的思路
// 时间复杂度: O(n)
// 空间复杂度: O(1)
class Solution {
    public int minSubArrayLen(int s, int[] nums) {
        int l= 0,r = -1;    // nums[l...r]为我们的滑动窗口
        int sum = 0;
        int result = nums.length + 1;
        while (l < nums.length){ // 窗口的左边界在数组范围内,则循环继续

            if( r+1 <nums.length && sum < s){
                r++;
                sum += nums[r];
            }else { // r已经到头 或者 sum >= s
                sum -= nums[l];
                l++;
            }

            if(sum >= s){
                result = (r-l+1) < result ? (r-l+1) : result ;
            }
        }
        if(result==nums.length+1){
            return 0;
        }
        return result;
    }
}

面试题28--字符串的排列

题目描述

输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

输入描述:

输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。

分析:看成两步:(1)首先求所有可能出现再第一个位置上的字符,即把第一个字符与后面的所有字符交换;(2)固定第一个字符,求后面所有字符的排列;

import java.util.*;
public class Solution {
    
    public ArrayList<String> Permutation(String str) {
        ArrayList<String> arraylist = new ArrayList<String>();
        if(str.length()==0) return arraylist;
        
        char[] str2= str.toCharArray();//转换成字符数组
        Permutation( str2,arraylist,0);
        Collections.sort(arraylist);//最后再对结果排个序,对集合排序用Collections.sort
        return arraylist;
    }
    public void Permutation(char[] str,ArrayList<String> arraylist,int count){
        String s = new String(str);
        if(count == str.length-1 && (!arraylist.contains(s))){//注意要判断是否已经存在了
            arraylist.add(s);
            return ;
        }
        
        for(int i = count ;i<str.length;i++){
            char ch = str[count];
            str[count] = str[i];
            str[i] = ch;
            //换了一次就转向下一个
            Permutation(str, arraylist,count+1);
            
            ch = str[count];//再交换回来
            str[count] = str[i];
            str[i] = ch;
        }
    }
}

本题扩展:

如果是求字符的所有组合,应该怎么做?

相关题目:

类似的:leetcode-46题 全排列

Given a collection of distinct integers, return all possible permutations.

Example:

Input: [1,2,3]
Output:
[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]
class Solution {
    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        //考虑边界
        int len = nums.length;
        if(len == 0) return res;
        
        dfs(res , nums, 0);
        //Collections.sort(res);//这里不需要排序,只需要都列举出来即可,如果需要排序,这样写也是不对的,
        return res;
    }
    public void dfs(List<List<Integer>> res , int[] nums , int c){
        int len = nums.length;
        if(c==len-1){//到头了
            List<Integer> list = new ArrayList<Integer>();
            for(int i = 0;i<len;i++){
                list.add(nums[i]);
            }
            res.add(list);
        }
        
        //如果需要结果是排好序的,那么就要再这里对c后面的数字做一个排序
        for(int i = c;i<len;i++){//易错:这里是从c开始,不是从c+1开始的,因为它自己本身也是一种情况
            int t = nums[c];
            nums[c] = nums[i];
            nums[i] = t;
            
            dfs(res,nums,c+1);
            
            t = nums[c];
            nums[c] = nums[i];
            nums[i] = t;
        }
    }
}

leetcode47--全排列II

Input: [1,1,2]
Output:
[
  [1,1,2],
  [1,2,1],
  [2,1,1]
]

别人的代码:

public class Solution {
    public List<List<Integer>> permuteUnique(int[] nums) {
        List<List<Integer>> res = new ArrayList<List<Integer>>();//外面一层是ArrayList 里面是LinkedList
        Arrays.sort(nums);
        LinkedList<Integer> list = new LinkedList<Integer>();//适合在中间删除和插入
        for (int num : nums) list.add(num);
        permute(list, 0, res);
        return res;
    }
    private void permute(LinkedList<Integer> nums, int start, List<List<Integer>> res){
        if (start == nums.size() - 1){
            res.add(new LinkedList<Integer>(nums));//把数组转换为LinkedList
            return;
        }
        for (int i = start; i < nums.size(); i++){
            if (i > start && nums.get(i) == nums.get(i - 1)) continue;//如果当前值和上一个值相等,就继续
            nums.add(start, nums.get(i));
            nums.remove(i + 1);
            permute(nums, start + 1, res);
            nums.add(i + 1, nums.get(start));
            nums.remove(start);
        }
    }
}

ArrayList                                VS         LinkedList

数组                                                       链表

适合检索和再末尾插入或删除      适合再中间插入或删除

LinkedLIst还实现了queue接口,还提供peek() pool() offer()等方法

看着别人的代码,修该自己的:

class Solution {
    //其实本题目转换成字符串会更简单,但是会消耗空间
    public List<List<Integer>> permuteUnique(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        //考虑边界
        int len = nums.length;
        if(len == 0) return res;
        
        Arrays.sort(nums);//改进:先排序了
        LinkedList<Integer> numlist = new LinkedList<Integer>();
        for(int i = 0;i<len;i++) numlist.add(nums[i]);
        dfs(res , numlist, 0);
        return res;
    }
    public void dfs(List<List<Integer>> res , LinkedList<Integer> numlist , int c){
        int len = numlist.size();
        if(c==len-1){//到头了
            res.add(new LinkedList<Integer>(numlist));
            
        }
        
        //如果需要结果是排好序的,那么就要再这里对c后面的数字做一个排序
        for(int i = c;i<len;i++){//易错:这里是从c开始,不是从c+1开始的,因为它自己本身也是一种情况
            if(i>c && numlist.get(i)==numlist.get(i-1)) continue;
            numlist.add(c,numlist.get(i));
            numlist.remove(i+1);
            
            dfs(res,numlist,c+1);
            
            numlist.add(i+1,numlist.get(c));
            numlist.remove(c);
        }
    }
}

leetcode--31:下一个排列

31. Next Permutation

Medium

Implement next permutation, which rearranges numbers into the lexicographically next greater permutation of numbers.

If such arrangement is not possible, it must rearrange it as the lowest possible order (ie, sorted in ascending order).

The replacement must be in-place and use only constant extra memory.

Here are some examples. Inputs are in the left-hand column and its corresponding outputs are in the right-hand column.

1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1

分析:思路都在注释里

/**
第一步:从后往前遍历,遇到后面的数字大于前面的数字的时候,就调换位置,然后返回;

如果第一步走完了,都没有换位置,说明原数组是按照降序排列的,那么换位置变为升序(替换要就地:最后一位与第一位替换位置)
**/
class Solution {
    public void nextPermutation(int[] nums) {
        int len = nums.length;
        //第一步
        for(int i = len-1;i>0;i--){
            if(nums[i]>nums[i-1]){
                int first = i-1;
                //寻找first后面比first大的数字,并交换位置
                for(int j = len-1;j>=first+1;j--){
                    if(nums[j]>nums[first]){
                        int t = nums[j];
                        nums[j] = nums[first];
                        nums[first] = t;
                        break ;
                    }
                }
                //再把first后面的(i ~ len-1)sort一下:按照从小到大的顺序排序
                for (int k = i; k < len-1; k++) {   
                    for (int j = k + 1; j < len; j++) {   
                        if (nums[k] > nums[j]) { // 交换两数的位置   

                            int t = nums[k];   
                            nums[k] = nums[j];   
                            nums[j] = t;   
                        }   
                    }   
                }
                return ;
                
            }
        }
        //把降序变为升序
        int t = 0;
        for(int i = 0;i<len/2;i++){
            t = nums[i];
            nums[i] = nums[len-1-i];
            nums[len-1-i] = t;
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_39474604/article/details/92965539
今日推荐