JAVA入门算法题(十三)

一、出现一次的数

题目:

/**
 * 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
 * 说明:
 * 你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
 * 示例 1:
 * 输入: [2,2,1]
 * 输出: 1
 * 示例 2:
 * 输入: [4,1,2,1,2]
 * 输出: 4
 */

 看完题便想到了,双重for循环,找到出现两次的数就跳出内层循环,没找到就返回该数

这里用到了flag,这种编程方式虽然好用,但是大量使用容易造成代码可读性下降,所以不建议使用

    public static int method1(int[] nums) {
        flag1:for (int i=0;i<nums.length;i++){
            for (int j=0;j<nums.length;j++){
                if (nums[i]==nums[j]&&i!=j){
                    continue flag1;
                }
            }
            return nums[i];
        }
        return nums[nums.length-1];
    }

第二种思路:任何一个数异或自己两次等于自身

比如:a=10,a^=5,a^=5

最后a还是等于10

所以可以遍历每一个数,用0异或所有的数,因为0异或任何数都等于对方

    public static int method2(int[] nums){
        int a=0;
        for(int i:nums){
            a^=i;
        }
        return a;
    }

很简洁

二、成环的链表

题目:

/**
 * 给定一个链表,判断链表中是否有环。
 * 链表中数字各不相同
 */
class ListNode {
    int val;
    ListNode next;

    ListNode(int x) {
        val = x;
        next = null;
    }
}

 可以根据链表中数字各不相同入手,使用set,一旦碰到数字在set中出现过就返回false,走完就返回true

    public boolean method1(ListNode head) {
        Set<ListNode>node = new HashSet<>();
        while(head!=null)
        {
            if(node.contains(head))
                return true;
            else
                node.add(head);
            head = head.next;
        }
        return false;
    }

可以从环的特征入手,在一个环形赛道上跑步,跑的快的必定可以超过慢的一圈,使用快慢指针

    public boolean method2(ListNode head){
        if (head == null || head.next == null) {
            return false;
        }
        ListNode fast = head.next;
        ListNode slow = head;
        while(fast != slow){
            //如果快指针把整个赛道都跑完了,跑到了null,那说明这个赛道是有尽头的。
            if (fast == null || fast.next == null) {
                return false;
            }
            fast = fast.next.next;
            slow = slow.next;
        }
        return true;
    }

可以使用一种标记,在走过的地方留下标记,如果遇到了之前留下的标记,说明自己跑了一个圈

    public boolean method3(ListNode node){
        while (node!=null){
            if (node.val==Integer.MIN_VALUE){
                return true;
            }else {
                node.val=Integer.MIN_VALUE;
            }
            node=node.next;
        }
        return false;
    }

三、最小栈

/**
 * 设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。
 *     push(x) -- 将元素 x 推入栈中。
 *     pop() -- 删除栈顶的元素。
 *     top() -- 获取栈顶元素。
 *     getMin() -- 检索栈中的最小元素。
 *
 * 示例:
 * MinStack minStack = new MinStack();
 * minStack.push(-2);
 * minStack.push(0);
 * minStack.push(-3);
 * minStack.getMin();   --> 返回 -3.
 * minStack.pop();
 * minStack.top();      --> 返回 0.
 * minStack.getMin();   --> 返回 -2.
 */

实现栈可以使用数组也可以使用链表,很显然使用链表实现简单一些。

这道题的核心在于如何保存最小值,并在插入删除时及时更新

一个很简单的方法便是将当前的最小值存到当前要插入的节点当中,这样删除时也就更新了最小值

    MyListNode listNode;
    public MinStack() {

    }

    public void push(int x) {
        if (listNode==null){
            listNode=new MyListNode(x,x);
        }else {
            MyListNode myListNode=new MyListNode(x,Math.min(listNode.min,x));
            myListNode.next=listNode;
            listNode=myListNode;
        }
    }

    public void pop() {
        if (listNode!=null){
            listNode=listNode.next;
        }
    }

    public int top() {
        return listNode.val;
    }

    public int getMin() {
        if (listNode==null){
            return Integer.MAX_VALUE;
        }else {
            return listNode.min;
        }
    }

    class MyListNode {
        int val;
        int min;
        MyListNode next;

        MyListNode(int x,int min) {
            val = x;
            next = null;
            this.min=min;
        }
    }

四、相交链表

/**
 * 编写一个程序,找到两个单链表相交的起始节点。
 * 示例 1:
 * 输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
 * 输出:Reference of the node with value = 8
 * 输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
 * 示例 2:
 * 输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
 * 输出:Reference of the node with value = 2
 * 输入解释:相交节点的值为 2 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
 * 示例 3:
 * 输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
 * 输出:null
 * 输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
 * 解释:这两个链表不相交,因此返回 null。
 * 注意:
 * 如果两个链表没有交点,返回 null.
 * 在返回结果后,两个链表仍须保持原有的结构。
 * 可假定整个链表结构中没有循环。
 * 程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。
 */

 相交链表特点:一旦两个链表相交,那么从交点到末尾,两个链表的这部分是相同的

问题是如何满足O(n)的时间复杂度和O(1)的内存使用

定义两个指针, 第一轮让两个到达末尾的节点指向另一个链表的头部, 最后如果相遇则为交点(在第一轮移动中恰好抹除了长度差)两个指针等于移动了相同的距离, 有交点就返回, 无交点就是各走了两条指针的长度

    public ListNode method1(ListNode headA, ListNode headB) {
        if(headA == null || headB == null) return null;
        ListNode pA = headA, pB = headB;
        while(pA != pB) {
            pA = pA == null ? headB : pA.next;
            pB = pB == null ? headA : pB.next;
        }
        return pA;
    }

另一种思路:先求两个链表的长度,让长的那个先走它俩的长度差,然后两个指针一起走,碰到一起就返回,碰不到就返回null

    public ListNode method3(ListNode headA, ListNode headB) {
        int len1 = getLength(headA), len2 = getLength(headB);
        while(len1 > len2) {
            headA = headA.next;
            len1--;
        }
        while(len1 < len2) {
            headB = headB.next;
            len2--;
        }
        while(headA != headB) {
            headA = headA.next;
            headB = headB.next;
        }
        return headA;
    }

    private int getLength(ListNode head) {
        int length = 0;
        while(head != null) {
            head = head.next;
            length++;
        }
        return length;
    }

还有一种很奇妙的做法,其实也是先去除了两个链表的长度差,这种你需要在纸上画一画才会惊觉它的奇妙

    public ListNode method2(ListNode headA, ListNode headB) {
        if (headA == null || headB == null) {
            return null;
        }
        ListNode last = headB;
        while (last.next != null) {
            last = last.next;
        }
        last.next = headB;

        ListNode fast = headA;
        ListNode slow = headA;

        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            if (slow == fast) {
                slow = headA;
                while (slow != fast) {
                    slow = slow.next;
                    fast = fast.next;
                }
                last.next = null;
                return fast;
            }
        }
        last.next = null;
        return null;
    }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

幽蓝丶流月

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值