每日算法练习

Posted 没谱的曲

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了每日算法练习相关的知识,希望对你有一定的参考价值。

1、删除链表的倒数第N个结点

给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。

示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.

当删除了倒数第二个节点后,链表变为 1->2->3->5.

说明:给定的 n 保证是有效的。

提升:使用栈实现或使用双指针实现

解析:

该题我选择使用双指针来解决,首先我们建立一个链表结构

public class ListNode 
        int val;
        ListNode next;

        ListNode() 
        

        ListNode(int val) 
            this.val = val;
        

        ListNode(int val, ListNode next) 
            this.val = val;
            this.next = next;
        
    

我们先创建一个虚拟头结点,然后创建p1、p2两个指针初始值为虚拟头结点

ListNode fictitious = new ListNode(-1);
        fictitious.next = head;

        ListNode p1 = fictitious;
        ListNode p2 = fictitious;

我们先让p1走n+1个节点,之后再让p1和p2两个指针同时移动,直到p1指针指向末尾(此时p2节点指向的就是删除节点的上一个节点)

for (int i = 0; i < n ; i++) //p1节点先走n+1步
            p1 = p1.next;
        
        // 记住 待删除节点p2 的上一节点
        ListNode prev = null;
        while (p1 != null) //两个节点同时移动直到p1节点走到链表的末尾
            prev = p2;
            p2 = p2.next;
            p1 = p1.next;
        

最后,删除链表的倒数第N个节点,输出链表

// 上一节点的next指针绕过 待删除节点p2 直接指向p2的下一节点
        prev.next = p2.next;
        // 释放 待删除节点p2 的next指针
        p2.next = null;

        return fictitious.next;

完整代码如下:

public class DeleteN 
    public class ListNode 
        int val;
        ListNode next;

        ListNode() 
        

        ListNode(int val) 
            this.val = val;
        

        ListNode(int val, ListNode next) 
            this.val = val;
            this.next = next;
        
    

    public ListNode removeNthFromEnd(ListNode head, int n) 
        ListNode fictitious = new ListNode(-1);
        fictitious.next = head;

        ListNode p1 = fictitious;
        ListNode p2 = fictitious;
        for (int i = 0; i < n ; i++) //p1节点先走n+1步
            p1 = p1.next;
        
        // 记住 待删除节点p2 的上一节点
        ListNode prev = null;
        while (p1 != null) //两个节点同时移动直到p1节点走到链表的末尾
            prev = p2;
            p2 = p2.next;
            p1 = p1.next;
        
        // 上一节点的next指针绕过 待删除节点p2 直接指向p2的下一节点
        prev.next = p2.next;
        // 释放 待删除节点p2 的next指针
        p2.next = null;

        return fictitious.next;
    


2、在排序数组中查找数字(使用二分法)

统计一个数字在排序数组中出现的次数。

输入: nums = [5,7,7,8,8,10], target = 8
输出: 2

输入: nums = [5,7,7,8,8,10], target = 6
输出: 0

二分查找:

我们先来了解一下二分查找
直接在百度百科上查找,可以得出以下二分法的描述:

二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。

解析:

该题的数组已经是排序好了的,整个数组都是单调递增,这样就能完美适用二分法。
我们首先定义一个开始下标和一个结束下标,分别指向第一个元素和最后一个元素,定义一个count来记录最后要输出的该数出现的次数

int start = 0;//定义数组开始下标
        int end = nums.length - 1;//定义数组结束下标
        int count = 0;//target出现的次数,默认为0

从数组中间将数组分成两份,读取中间的数,若查找的数大于中间的数,则将开始下标移动到中间,否则将结束下标移动到中间,直到两个下标重合

while (start < end) //当start不小于end时,说明位置重合,跳出循环
            int mid = (start + end) / 2;
            if (nums[mid] >= target) 
                end = mid;
             else if (nums[mid] < target) //将值小于target的所有数排除在下标start的元素前
                start = mid + 1;
            
        

从下标为start的元素开始往后遍历,直到遍历完整个数组或遍历到的值不等于target为止

while (start < nums.length && nums[start] == target) //从下标为start的元素开始往后遍历,直到遍历完整个数组或遍历到的值不等于target为止
            start++;
            count++;
        
        return count;

完成后的完整代码如下:

public class BinarySearch 
    public int search(int[] nums, int target) 
        int start = 0;//定义数组开始下标
        int end = nums.length - 1;//定义数组结束下标
        int count = 0;//target出现的次数,默认为0
        while (start < end) //当start不小于end时,说明位置重合,跳出循环
            int mid = (start + end) / 2;
            if (nums[mid] >= target) 
                end = mid;
             else if (nums[mid] < target) //将值小于target的所有数排除在下标start的元素前
                start = mid + 1;
            
        
        while (start < nums.length && nums[start] == target) //从下标为start的元素开始往后遍历,直到遍历完整个数组或遍历到的值不等于target为止
            start++;
            count++;
        
        return count;
    


现在我们调用这个方法测试一下:

public static void main(String[] args) 
        BinarySearch binarySearch = new BinarySearch();
        int[] nums = 5,7,7,8,8,10;
        System.out.println(binarySearch.search(nums, 8));
    

测试结果:

3、用两个栈实现队列

用两个栈来实现一个队列,分别完成在队列尾部插入整数(push)和在队列头部删除整数(pop)的功能。
队列中的元素为int类型。保证操作合法,即保证pop操作时队列内已有元素。

示例:
输入:
[“PSH1”,“PSH2”,“POP”,“POP”]
返回:
1,2
解析:
“PSH1”:代表将1插入队列尾部
“PSH2”:代表将2插入队列尾部
"POP“:代表删除一个元素,先进先出=>返回1
"POP“:代表删除一个元素,先进先出=>返回2

解析:

该题看似困难,其实分析后就会感觉非常简单
我们首先要了解栈和队列的关系:

  • 栈:是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
  • 队列:队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。

简单来说,栈就是只有一个口,这个口不仅是入口也是出口,先入后出,后入先出
而队列有两个口,一个为入口,一个为出口,只能先从入口存数据,才能从出口出,先入先出,后入后出
那么我们要如何通过两个栈来实现队列呢?
首先我们创建两个栈

Stack<Integer> stack1;
    Stack<Integer> stack2;
    public CQueue() 
        stack1 = new Stack<>();
        stack2 = new Stack<>();
    

我们将一个栈用来实现插入操作,而另一个栈用来实现输出操作。
当我们要进行插入操作的时候直接调用方法往第一个栈中存数据

    public void psh(int value) 
        stack1.add(value);
    

当我们要进行删除操作的时候

  1. 如果stack2为空,我们就将stack1中的所有元素取出,存入stack2中
  2. 如果stack2仍为空,返回-1,否则从stack2中弹出一个值
public int pop() 
        if (stack2.isEmpty()) 
            if (stack1.isEmpty()) return -1;
            while (!stack1.isEmpty()) 
                stack2.add(stack1.pop());
            
            return stack2.pop();
         else return stack2.pop();
    

完成后的完整代码如下:

import java.util.Stack;

public class CQueue 

    Stack<Integer> stack1;
    Stack<Integer> stack2;
    public CQueue() 
        stack1 = new Stack<>();
        stack2 = new Stack<>();
    


    public void psh(int value) 
        stack1.add(value);
    

    public int pop() 
        if (stack2.isEmpty()) 
            if (stack1.isEmpty()) return -1;
            while (!stack1.isEmpty()) 
                stack2.add(stack1.pop());
            
            return stack2.pop();
         else return stack2.pop();
    

调用两个方法测试一下

public static void main(String[] args) 
        CQueue cQueue = new CQueue();
        cQueue.psh(1);
        cQueue.psh(2);
        System.out.println(cQueue.pop());
        System.out.println(cQueue.pop());
    

测试结果:

算法之路漫漫,多加练习,才能提升自己!

以上是关于每日算法练习的主要内容,如果未能解决你的问题,请参考以下文章

每日算法练习(2020-1-11)

每日算法练习(2020-1-27)

每日算法练习

每日算法练习

每日算法练习

每日算法练习(2020-1-10)