一:排序
归并排序的应用
1. 小和问题
在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。
public static int minSum(int[] arr,int low,int high){
if (low >= high)
return 0;
int sum = 0;
int mid = low + ((high - low) / 2);
//分别求出左边的子数组和右边的子数组中各自的最小和
sum += minSum(arr,low,mid);
sum += minSum(arr,mid+1,high);
//再对合并后的数组求最小和
sum += min(arr,low,mid,high);
return sum;
}
public static int min(int[] arr,int low,int mid,int high) {
int sum = 0;
int [] temp = new int[high - low + 1];
int k = 0;
int i,j;
//有较小的值就加上
for (i = low,j = mid+1; i <= mid && j <= high;) {
if (arr[i] < arr[j]){
temp[k++] = arr[i];
sum += arr[i++] * (high - j + 1);
}
else
temp[k++] = arr[j++];
}
while (i <= mid)
temp[k++] = arr[i++];
while (j <= high)
temp[k++] = arr[j++];
i = low;
for (k = 0; k <temp.length; k++) {
arr[i++] = temp[k];
}
return sum;
}
2. 逆序对问题
在一个数组中,左边的数如果比右边的数大,则折两个数构成一个逆序对,请打印所有逆序对。
public static void Reverse(int[] arr,int low,int high){
if (low >= high)
return;
int mid = low + ((high - low) / 2);
//分别求出左边的子数组和右边的子数组中各自的逆序对
Reverse(arr,low,mid);
Reverse(arr,mid+1,high);
//再对合并后的数组求逆序对
reverse(arr,low,mid,high);
}
public static void reverse(int[] arr,int low,int mid,int high) {
int [] temp = new int[high - low + 1];
int k = 0;
int i,j;
//打印逆序对
for (i = low,j = mid+1; i <= mid && j <= high;) {
if (arr[i] < arr[j])
temp[k++] = arr[i++];
else
{
int x = i;
while (x <= mid)
System.out.print("["+arr[x++]+","+arr[j]+"]\t");
System.out.println();
temp[k++] = arr[j++];
}
}
while (i <= mid)
temp[k++] = arr[i++];
while (j <= high)
temp[k++] = arr[j++];
i = low;
for (k = 0; k <temp.length; k++) {
arr[i++] = temp[k];
}
}
二:查找
1. 二分查找
【题目】 定义局部最小的概念。arr长度为1时,arr[0]是局部最小。arr的长度为 N(N>1)时,如果arr[0]<arr[1],那么arr[0]是局部最小;如果arr[N1]<arr[N-2],那么arr[N-1]是局部最小;如果0<i<N-1,既有 arr[i]<arr[i-1],又有arr[i]<arr[i+1],那么arr[i]是局部最小。 给定无序数组arr,已知arr中任意两个相邻的数都不相等。写一个函数, 只需返回arr中任意一个局部最小出现的位置即可。
public static int getPartMin(int[] arr){
if (arr == null || arr.length == 0)
return -1;
if (arr.length == 1)
return 0;
return getPartMin(arr,0,arr.length - 1);
}
public static int getPartMin(int[] arr,int low,int high){
if (low > high)
return -1;
if (arr[low] < arr[low+1])
return low;
if (arr[high] < arr[high-1])
return high;
return getPartMin(arr,low+1,high-1);
}
三:线性表
1. 用数组结构实现大小固定的队列和栈
2. 实现一个特殊的栈
在实现栈的基本功能的基础上,再实现返 回栈中最小元素的操作。
【要求】
- pop、push、getMin操作的时间复杂度都是O(1)。
- 设计的栈类型可以使用现成的栈结构
public class MinStack {
private Stack<Integer> stack;
private Stack<Integer> minStack;
public MinStack() {
stack = new Stack<>();
minStack = new Stack<>();
}
public void push(int num){
stack.push(num);
if (minStack.isEmpty() || minStack.peek() >= num)
minStack.push(num);
}
public int peek(){
return stack.peek();
}
public int pop(){
int num = stack.pop();
if (!minStack.isEmpty() && minStack.peek() == num)
minStack.pop();
return num;
}
public int min(){
return minStack.peek();
}
}
3. 队列和栈的转换
如何仅用队列结构实现栈结构?
/**
* 用队列实现栈
*/
public class QueueToStack {
private Queue<Integer> in;
private Queue<Integer> out;
public QueueToStack() {
in = new LinkedList<>();
out = new LinkedList<>();
}
public void push(int num){
in.offer(num);
}
public int peek(){
while (in.size() > 1)
out.offer(in.poll());
int num = in.poll();
out.offer(num);
swap();
return num;
}
public int pop(){
while (in.size() > 1)
out.offer(in.poll());
int num = in.poll();
swap();
return num;
}
private void swap(){
Queue<Integer> temp = in;
in = out;
out = temp;
}
}
如何仅用栈结构实现队列结构?
/**
* 用栈实现队列
*/
public class StackToQueue {
private Stack<Integer> in;
private Stack<Integer> out;
public StackToQueue() {
in = new Stack<>();
out = new Stack<>();
}
public void offer(int num){
in.push(num);
}
public int peek(){
if (out.isEmpty()){
while (!in.isEmpty())
out.push(in.pop());
}
return out.peek();
}
public int poll(){
if (out.isEmpty()){
while (!in.isEmpty())
out.push(in.pop());
}
return out.pop();
}
}
4. 实现一种狗猫队列的结构
要求如下:
- 用户可以调用add方法将cat类或dog类的实例放入队列中;
- 用户可以调用pollAll方法,将队列中所有的实例按照进队列的先后顺序依次弹出;
- 用户可以调用pollDog方法,将队列中dog类的实例按照进队列的先后顺序依次弹出;
- 用户可以调用pollCat方法,将队列中cat类的实例按照进队列的先后顺序依次弹出;
- 用户可以调用isEmpty方法,检查队列中是否还有dog或cat的实例;
- 用户可以调用isDogEmpty方法,检查队列中是否有dog类的实例;
- 用户可以调用isCatEmpty方法,检查队列中是否有cat类的实例。
/**
* 猫狗队列
*/
class Animal{
private String type;
Animal(String type) {
this.type = type;
}
String getType() {
return type;
}
}
class Cat extends Animal{
private String name;
Cat(String name) {
super("cat");
this.name = name;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}
class Dog extends Animal{
private String name;
Dog(String name) {
super("dog");
this.name = name;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
'}';
}
}
/**
* 自定义扩展类,添加时间戳字段,用于区分入队时间
*/
class AnimalCount{
private Animal animal;
private long count;
AnimalCount(Animal animal, long count) {
this.animal = animal;
this.count = count;
}
Animal getAnimal() {
return animal;
}
long getCount() {
return count;
}
public String getType(){
return animal.getType();
}
}
public class CatAndDogQueue {
private Queue<AnimalCount> dogs = new LinkedList<>();
private Queue<AnimalCount> cats = new LinkedList<>();
//记录时间戳
private int count;
void add(Animal animal){
AnimalCount animalCount = new AnimalCount(animal,count++);
if (animal.getType().equals("cat"))
cats.add(animalCount);
else
dogs.add(animalCount);
}
Animal pollAll(){
Animal temp;
if(isEmpty())
return null;
else if (isDogEmpty())
temp = cats.poll().getAnimal();
else if(isCatEmpty())
temp = dogs.poll().getAnimal();
else
temp = dogs.peek().getCount() > cats.peek().getCount() ? cats.poll().getAnimal() : dogs.poll().getAnimal();
return temp;
}
Cat pollCat(){
if (isCatEmpty())
return null;
return (Cat) cats.poll().getAnimal();
}
Dog pollDog(){
if (isDogEmpty())
return null;
return (Dog) dogs.poll().getAnimal();
}
private boolean isEmpty(){
return cats.isEmpty() || dogs.isEmpty();
}
private boolean isDogEmpty(){
return dogs.isEmpty();
}
private boolean isCatEmpty(){
return cats.isEmpty();
}
public static void main(String[] args) {
Cat cat1 = new Cat("c1");
Dog dog1 = new Dog("d1");
Cat cat2 = new Cat("c2");
Dog dog2 = new Dog("d2");
CatAndDogQueue queue = new CatAndDogQueue();
queue.add(cat1);
queue.add(dog1);
queue.add(cat2);
queue.add(dog2);
System.out.println(queue.pollAll());
System.out.println(queue.pollAll());
System.out.println(queue.pollCat());
System.out.println(queue.pollDog());
}
}
5. 设计RandomPool结构
【题目】 设计一种结构,在该结构中有如下三个功能:
- insert(key):将某个key加入到该结构,做到不重复加入。
- delete(key):将原本在结构中的某个key移除。
- getRandom():等概率随机返回结构中的任何一个key。
【要求】 Insert、delete和getRandom方法的时间复杂度都是O(1)。
/**
* 随机数据池
*/
public class RandomPool<Key> {
private HashMap<Key,Integer> keys;
private HashMap<Integer,Key> indexs;
private int size;
public RandomPool(){
keys = new HashMap<>();
indexs = new HashMap<>();
}
public void insert(Key key){
keys.put(key,size);
indexs.put(size,key);
size++;
}
//把要删除的键与最后一个键交换,再删除
public void delete(Key key){
if (!keys.containsKey(key))
return;
int index = keys.get(key);
Key temp = indexs.get(size-1);
keys.put(temp,index);
keys.remove(key);
indexs.put(index,temp);
indexs.remove(size-1);
size--;
}
public Key getRandom(){
int ran = (int)(Math.random()*(size));
return indexs.get(ran);
}
}
6. 转圈打印矩阵
【题目】 给定一个整型矩阵matrix,请按照转圈的方式打印它。
例如: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
打印结果为:1,2,3,4,8,12,16,15,14,13,9,5,6,7,11, 10
【要求】 额外空间复杂度为O(1)。
/**
* 环形打印矩形
*/
public class circleMatrix {
public static void print(int[][] matrix,int x1,int y1,int x2,int y2){
//base case
if (x1 > x2 || y1 > y2)
return;
//只有一行
else if (y1 == y2){
for (int i = x1; i < x2; i++) {
System.out.printf(matrix[i][y1]+",");
}
}
//只有一列
else if (x1 == x2){
for (int i = y1; i < y2; i++) {
System.out.printf(matrix[x1][i]+",");
}
}
//先从左向右打印,再从上到下打印,之后从右往左打印,从下往上打印
else {
int i = x1,j = y1;
while (j < y2){
System.out.printf(matrix[i][j++]+",");
}
while (i < x2){
System.out.printf(matrix[i++][j]+",");
}
while (j > y1){
System.out.printf(matrix[i][j--]+",");
}
while (i > x1){
System.out.printf(matrix[i--][j]+",");
}
//再将左上角和右下角的边界减小
print(matrix,x1+1,y1+1,x2-1,y2-1);
}
}
}
7. “之”字形打印矩阵
【题目】 给定一个矩阵matrix,按照“之”字形的方式打印这个矩阵
例如: 1 2 3 4 5 6 7 8 9 10 11 12 “
之”字形打印的结果为:1,2,5,9,6,3,4,7,10,11,8,12
【要求】 额外空间复杂度为O(1)。
/**
* 之字形打印矩阵
*/
public class zhiMatrix {
public static void print(int[][] matrix,boolean flag,int x1,int y1){
if (matrix == null || matrix[0] == null)
return;
if (y1 < 0 && x1 < 0)
return;
//临界条件,目标点超过右下角
if (x1 > matrix.length - 1 && y1 > matrix[0].length - 1)
return;
if (flag){
//目标超过右界,从左边继续向下
if (y1 == matrix[0].length)
{
y1--;
x1++;
}
//从右上角向左下角打印
while (y1 >= 0 && x1 <= matrix.length - 1 && y1 <= matrix[0].length - 1)
System.out.printf(matrix[x1++][y1--]+",");
print(matrix,false,x1,y1+1);
}
else {
//目标超过下界,从右边继续继续向上
if (x1 == matrix.length)
{
x1--;
y1++;
}
//从左下角向右上角打印
while (x1 >= 0 && y1 <= matrix[0].length - 1 && x1 <= matrix.length - 1)
System.out.printf(matrix[x1--][y1++]+",");
print(matrix,true,x1+1,y1);
}
}
}
8. 在行列都排好序的矩阵中找数
【题目】 给定一个有N*M的整型矩阵matrix和一个整数K,matrix的每一行和每一 列都是排好序的。实现一个函数,判断K是否在matrix中。
例如: 0 1 2 5 2 3 4 7 4 4 4 8 5 7 7 9 如果K为7,返回true;
如果K为6,返回false。
【要求】 时间复杂度为O(N+M),额外空间复杂度为O(1)。
/**
* 在行列都排好序的矩阵中找数
*/
public class FindNumber {
public static boolean isExits(int[][] matrix,int num,int x,int y){
//如果到达最左边或者到达最下边,失败
if (x >= matrix.length || y < 0)
return false;
//右边的数比较大,往左边的找
if (matrix[x][y] > num)
return isExits(matrix,num,x,y-1);
//上边的数比较小,往下边的找
else if (matrix[x][y] < num)
return isExits(matrix,num,x+1,y);
//找到了
else
return true;
}
public static void main(String[] args) {
int[][] matrix = {{0 ,1, 2 , 5}, {2, 3, 4 , 7}, {4, 4, 4 , 8} ,{5 , 7, 7, 9}};
System.out.println("7:"+isExits(matrix,7,0,matrix[0].length-1));
System.out.println("6:"+isExits(matrix,6,0,matrix[0].length-1));
}
}
9. 打印两个有序链表的公共部分
【题目】 给定两个有序链表的头指针head1和head2,打印两个链表的公共部分。
**
* 打印两个有序链表的公共部分
*/
public class CommonLink {
static class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
@Override
public String toString() {
return "ListNode{" +
"val=" + val +
", next=" + next +
'}';
}
}
//使用外排法,逐个查找
public static void print(ListNode head1,ListNode head2){
boolean found = false;
while (head1 != null && head2 != null){
if (head1.val < head2.val)
head1 = head1.next;
else if (head1.val > head2.val)
head2 = head2.next;
else
{
System.out.printf(head1.val+" ");
head1 = head1.next;
head2 = head2.next;
found = true;
continue;
}
//如果之前有找到,则现在的是新的公共部分,换行
if (found)
{
System.out.println();
found = false;
}
}
}
10. 判断一个链表是否为回文结构
【题目】 给定一个链表的头节点head,请判断该链表是否为回文结构。 例如: 1->2->1,返回true。 1->2->2->1,返回true。 15->6->15,返回true。 1->2->3,返回false。
进阶: 如果链表长度为N,时间复杂度达到O(N),额外空间复杂度达到O(1)。
**
* 回文链表
*/
public class PalindromeLinked {
static class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
@Override
public String toString() {
return "ListNode{" +
"val=" + val +
", next=" + next +
'}';
}
}
public static boolean isPalindrome(ListNode head){
if (head == null)
return false;
if (head.next == null)
return true;
ListNode fast = head;
ListNode slow = head;
//快指针走两步,慢指针走一步
while (fast != null){
fast = fast.next;
if (fast != null)
fast = fast.next;
slow = slow.next;
}
//当快指针到尾时,慢指针到达中点位置
//使用两个记录分别前半段和后半段,再将后半段反转并与前半段逐个比较
ArrayList<ListNode> list1 = new ArrayList<>();
while (head != slow){
list1.add(head);
head = head.next;
}
ArrayList<ListNode> list2 = new ArrayList<>();
while (slow != null){
list2.add(slow);
slow = slow.next;
}
Collections.reverse(list2);
for (int i = 0; i < list1.size()-1; i++) {
if (list1.get(i).val != list2.get(i).val)
return false;
}
return true;
}
public static void main(String[] args) {
ListNode head = new ListNode(1);
ListNode node1 = new ListNode(2);
ListNode node2 = new ListNode(2);
ListNode node3 = new ListNode(1);
// head.next = node1;node1.next = node2;node2.next = node3;
// System.out.println(isPalindrome(head));
// head.next = node1;node1.next = node2;
// System.out.println(isPalindrome(head));
head.next = node1;node1.next = node3;
System.out.println(isPalindrome(head));
}
}
//栈法
public static boolean isPalindrome2(ListNode head){
if (head == null)
return false;
if (head.next == null)
return true;
ListNode fast = head;
ListNode slow = head;
//快指针走两步,慢指针走一步
while (fast != null){
fast = fast.next;
if (fast != null)
fast = fast.next;
else
continue;
slow = slow.next;
}
//当快指针到尾时,慢指针到达中点位置
//用快指针指向中点
fast = slow;
Stack<ListNode> stack = new Stack<>();
while (slow != null){
stack.push(slow);
slow = slow.next;
}
while (head != fast){
if (head.val != stack.pop().val)
return false;
head = head.next;
}
return true;
}
//空间复杂度O(1)
public static boolean isPalindrome3(ListNode head) {
if (head == null)
return false;
if (head.next == null)
return true;
ListNode fast = head;
ListNode slow = head;
//快指针走两步,慢指针走一步
while (fast != null){
fast = fast.next;
if (fast != null)
fast = fast.next;
else
continue;
slow = slow.next;
}
//当快指针到尾时,慢指针到达中点位置
//开始反转右部分的链表
fast = slow.next;
slow.next = null;
ListNode node = null;
while (fast != null){
node = fast.next;
fast.next = slow;
slow = fast;
fast = node;
}
//记录最右边的结点,方便恢复链表
fast = slow;
//逐个比较
boolean res = true;
while (head != null && slow != null){
if (head.val != slow.val)
{
res = false;
break;
}
head = head.next;
slow = slow.next;
}
//恢复右半部分
while (fast != null){
node = fast.next;
fast.next = slow;
slow = fast;
fast = node;
}
return res;
}
11. 将单向链表按某值划分成左边小、中间相等、右边大的形式
【题目】 给定一个单向链表的头节点head,节点的值类型是整型,再给定一个整 数pivot。实现一个调整链表的函数,将链表调整为左部分都是值小于 pivot的节点,中间部分都是值等于pivot的节点,右部分都是值大于 pivot的节点。除这个要求外,对调整后的节点顺序没有更多的要求。 例如:链表9->0->4->5->1,pivot=3。 调整后链表可以是1->0->4->9->5,也可以是0->1->9->5->4。总之,满 足左部分都是小于3的节点,中间部分都是等于3的节点(本例中这个部 分为空),右部分都是大于3的节点即可。对某部分内部的节点顺序不做 要求。
进阶: 在原问题的要求之上再增加如下两个要求。
在左、中、右三个部分的内部也做顺序要求,要求每部分里的节点从左 到右的顺序与原链表中节点的先后次序一致。 例如:链表9->0->4->5->1,pivot=3。调整后的链表是0->1->9->4->5。 在满足原问题要求的同时,左部分节点从左到右为0、1。在原链表中也 是先出现0,后出现1;中间部分在本例中为空,不再讨论;右部分节点 从左到右为9、4、5。在原链表中也是先出现9,然后出现4,最后出现5。
/**
* 划分链表
*/
public class DivideLinke {
static class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
@Override
public String toString() {
return "ListNode{" +
"val=" + val +
", next=" + next +
'}';
}
}
public static void divide(ListNode head,int pivot){
if (head == null)
return;
ListNode node = head;
ListNode temp = null;
Deque<ListNode> low = new ArrayDeque<>();
Deque<ListNode> mid = new ArrayDeque<>();
Deque<ListNode> high = new ArrayDeque<>();
//开始划分,根据值放入不同的队列
while (node != null){
temp = node.next;
node.next = null;
if (node.val < pivot)
{
if (!low.isEmpty())
low.peek().next = node;
low.push(node);
}
else if (node.val == pivot)
{
if (!mid.isEmpty())
mid.peek().next = node;
mid.push(node);
}
else
{
if (!high.isEmpty())
high.peek().next = node;
high.push(node);
}
node = temp;
}
//合并队列
if (!low.isEmpty())
{
node = low.peek();
head = low.peekLast();
}
if (!mid.isEmpty())
{
temp = mid.peekLast();
if (node != null)
node.next = temp;
node = temp;
if (low.isEmpty())
head = temp;
}
if (!high.isEmpty())
{
temp = high.peekLast();
if (node != null)
{
node.next = temp;
}
if (low.isEmpty() && mid.isEmpty())
head = temp;
}
System.out.println(head);
}
public static void main(String[] args) {
ListNode head = new ListNode(9);
ListNode node1 = new ListNode(0);
ListNode node2 = new ListNode(4);
ListNode node3 = new ListNode(5);
ListNode node4 = new ListNode(1);
head.next = node1;
node1.next = node2;
node2.next = node3;
node3.next = node4;
System.out.println(head);
divide(head,3);
}
}
如果链表长度为N,时间复杂度请达到O(N),额外空间复杂度请达到O(1)。
//空间复杂度O(1)
public static void divide1(ListNode head,int pivot) {
if (head == null)
return;
ListNode node = head;
//左、中、右三部分分别设置头、尾和中间结点
ListNode lowH = null;
ListNode lowTemp = null;
ListNode lowT = null;
ListNode midH = null;
ListNode midTemp = null;
ListNode midT = null;
ListNode highH = null;
ListNode highTemp = null;
ListNode highT = null;
//开始循环
while (node != null){
node = head.next;
head.next = null;
if (pivot > head.val)
{
if (lowH == null)
{
lowH = head;
lowTemp = head;
}
lowT = head;
if (lowTemp != lowT)
{
lowTemp.next = lowT;
lowTemp = lowT;
}
}
else if (pivot == head.val){
if (midH == null)
{
midH = head;
midTemp = head;
}
midT = head;
if (midTemp != midT)
{
midTemp.next = midT;
midTemp = midT;
}
}
else{
if (highH == null)
{
highH = head;
highTemp = head;
}
highT = head;
if (highTemp != highT)
{
highTemp.next = highT;
highTemp = highT;
}
}
head = node;
}
//合并链表
if (lowT != null)
{
head = lowH;
if (midH != null)
lowT.next = midH;
else
lowT.next = highH;
}
if (midT != null)
{
if (head == null)
head = midH;
midT.next = highH;
}
if (head == null)
head = highH;
System.out.println(head);
}
12. 复制含有随机指针节点的链表
Node类中的value是节点值,next指针和正常单链表中next指针的意义一 样,都指向下一个节点,rand指针是Node类中新增的指针,这个指针可 能指向链表中的任意一个节点,也可能指向null。 给定一个由Node节点类型组成的无环单链表的头节点head,请实现一个 函数完成这个链表中所有结构的复制,并返回复制的新链表的头节点。
public static ListNode copy(ListNode head){
if (head == null)
return null;
//使用一个哈希映射表映射旧的结点和新的结点,再单独处理next指针和rand指针
HashMap<ListNode,ListNode> nodes = new HashMap<>();
ListNode node = null;
ListNode temp = head;
ListNode newHead = null;
//构建键值对
while (temp != null){
node = new ListNode(temp.val);
if (newHead == null)
newHead = node;
nodes.put(temp,node);
temp = temp.next;
}
//单独处理结点的指针
while (head != null){
nodes.get(head).next = nodes.get(head.next);
nodes.get(head).rand = nodes.get(head.rand);
head = head.next;
}
return newHead;
}
进阶:不使用额外的数据结构,只用有限几个变量,且在时间复杂度为 O(N)内完成原问题要实现的函数。
public static ListNode copy1(ListNode head){
if (head == null)
return null;
ListNode node = null;
ListNode temp = head;
//在每个结点后面创建一个拷贝结点
while (temp != null){
node = new ListNode(temp.val);
node.next = temp.next;
temp.next = node;
temp = node.next;
}
//拷贝rand指针
temp = head;
while (temp != null){
if (temp.rand != null)
temp.next.rand = temp.rand.next;
temp = temp.next.next;
}
//分离链表
temp = head;
ListNode newHead = head.next;
ListNode next = newHead;
while (temp != null){
node = next;
temp.next = node.next;
temp = temp.next;
if (temp != null) {
next = temp.next;
node.next = next;
}
}
return newHead;
}
13. 两个单链表相交的一系列问题
【题目】 在本题中,单链表可能有环,也可能无环。给定两个单链表的头节点 head1和head2,这两个链表可能相交,也可能不相交。请实现一个函数, 如果两个链表相交,请返回相交的第一个节点;如果不相交,返回null 即可。 要求:如果链表1的长度为N,链表2的长度为M,时间复杂度请达到 O(N+M),额外空间复杂度请达到O(1)。
/**
* 相交链表
*/
public class CrossLink {
static class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
@Override
public String toString() {
return "ListNode{" +
"val=" + val +
", next=" + next.val +
'}';
}
}
//是否成环
private static ListNode isCircle1(ListNode head){
HashSet<ListNode> set = new HashSet<>();
while (head != null){
if (set.contains(head)){
return head;
}
set.add(head);
head = head.next;
}
return null;
}
//是否成环,空间复杂度O(1)
private static ListNode isCircle(ListNode head){
ListNode fast = head;
ListNode slow = head;
//快指针如果与慢指针相遇,则有环,改为走一步
boolean meet = false;
while (fast != null){
fast = fast.next;
if (fast!=null && !meet)
fast = fast.next;
slow = slow.next;
if (fast == slow)
if(!meet)
meet = true;
else
return fast;
}
return null;
}
//是否相交
public static ListNode isCross(ListNode head1,ListNode head2) {
if (head1 == null || head2 == null)
return null;
ListNode circle1 = isCircle1(head1);
ListNode circle2 = isCircle1(head2);
//一个有环,一个没环,不会相交
if ((circle1 == null && circle2 != null) || (circle1 != null && circle2 == null))
return null;
//两个都没环
else if (circle1 == null && circle2 == null)
return noCircle(head1,head2,null);
//两个都是环
else {
int n = 0;
ListNode temp1 = head1;
ListNode temp2 = head2;
//共享环,相交在非环区域
if (circle1 == circle2)
return noCircle(head1,head2,circle1);
else {
ListNode temp = circle1;
circle1 = circle1.next;
while (circle1 != temp) {
//共享环,相交在环区域
if (circle1 == circle2)
return circle1;
circle1 = circle1.next;
}
//非相交
return null;
}
}
}
//两个非环的链表
private static ListNode noCircle(ListNode head1,ListNode head2,ListNode cross){
int n = 0;
ListNode temp1 = head1;
ListNode temp2 = head2;
//先统计两个链表的长度
while (temp1.next != cross){
n++;
temp1 = temp1.next;
}
while (temp2.next != cross){
n--;
temp2 = temp2.next;
}
if (temp1 != temp2)
return null;
//较长的链表先走N步
temp1 = n >= 0 ? head1 : head2;
temp2 = temp1 == head2 ? head1 : head2;
n = Math.abs(n);
while (0 < n--){
temp1 = temp1.next;
}
//两个链表一起走,直到相交结点
while (temp1 != temp2){
temp1 = temp1.next;
temp2 = temp2.next;
}
return temp1;
}
public static void main(String[] args) {
ListNode head1 = new ListNode(1);
ListNode node1 = new ListNode(2);
ListNode node2 = new ListNode(3);
ListNode node3 = new ListNode(4);
ListNode node4 = new ListNode(5);
ListNode head2 = new ListNode(6);
ListNode node5 = new ListNode(7);
ListNode node6 = new ListNode(8);
ListNode node7 = new ListNode(9);
ListNode node8 = new ListNode(10);
//都无环
// head.next = node1;
// node1.next = node2;
// node2.next = node3;
// node3.next = node4;
// node5.next = node3;
// System.out.println(isCross(head,node5));
//都有环且不相干
// head1.next = node1;
// node1.next = node2;
// node2.next = node3;
// node3.next = node4;
// node4.next = node1;
// head2.next = node5;
// node5.next = node6;
// node6.next = node7;
// node7.next = node8;
// node8.next = node5;
// System.out.println(isCross(head1,head2));
//都有环且相干
// head1.next = node1;
// node1.next = node2;
// node2.next = node3;
// node3.next = node4;
// node4.next = node1;
// head2.next = node2;
// System.out.println(isCross(head1,head2));
//都有环且相干
head1.next = node1;
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node1;
head2.next = head1;
System.out.println(isCross(head1,head2));
}
}
14. 反转单向和双向链表
【题目】 分别实现反转单向链表和反转双向链表的函数。
【要求】 如果链表长度为N,时间复杂度要求为O(N),额外空间复杂度要求为O(1)
/**
* 反转链表
*/
public class ReverseLink {
static class ListNode1 {
int val;
ListNode1 next = null;
ListNode1(int val) {
this.val = val;
}
@Override
public String toString() {
return " ListNode1{" +
"val=" + val +
", next=" + next +
'}';
}
}
static class ListNode2 {
int val;
ListNode2 next = null;
ListNode2 prev = null;
ListNode2(int val) {
this.val = val;
}
@Override
public String toString() {
return " ListNode2{" +
"val=" + val +
", next=" + next +
", prev=" + (prev == null ? null : prev.val) +
'}';
}
}
//单链表
public static ListNode1 reverse1(ListNode1 head){
if (head == null)
return null;
ListNode1 node = head.next;
head.next = null;
ListNode1 temp = null;
while (node != null){
temp = node.next;
node.next = head;
head = node;
node = temp;
}
return head;
}
//双链表
public static ListNode2 reverse2(ListNode2 head) {
if (head == null)
return null;
ListNode2 next = head.next;
ListNode2 prev = head.prev;
head.next = null;
head.prev = null;
ListNode2 temp = null;
while (next != null){
temp = next.next;
next.next = head;
head.next = prev;
head.prev = next;
prev = head;
head = next;
next = temp;
}
head.prev = null;
return head;
}
public static void main(String[] args) {
//单链表
// ListNode1 head = new ListNode1(1);
// ListNode1 node1 = new ListNode1(2);
// ListNode1 node2 = new ListNode1(3);
// ListNode1 node3 = new ListNode1(4);
// ListNode1 node4 = new ListNode1(5);
// head.next = node1;
// node1.next = node2;
// node2.next = node3;
// node3.next = node4;
// System.out.println(head);
// System.out.println(reverse1(head));
//双链表
ListNode2 head = new ListNode2(1);
ListNode2 node1 = new ListNode2(2);
ListNode2 node2 = new ListNode2(3);
ListNode2 node3 = new ListNode2(4);
ListNode2 node4 = new ListNode2(5);
head.next = node1;
node1.next = node2;
node1.prev = head;
node2.next = node3;
node2.prev = node1;
node3.next = node4;
node3.prev = node2;
node4.prev = node3;
System.out.println(head);
System.out.println(reverse2(head));
}
}