算法入门-常见的算法

一 算法入门

1 常见位运算

  1. 位与(&):二元运算符,两个为1时结果为1,否则为0
  2. 位或(|):二元运算符,两个其中有一个为1时结果就为1,否则为0
  3. 位异或(^):二元运算符,两个数同时为1或0时结果为1,否则为0 (可以理解为无进位相加)
  4. 位取非(~):一元运算符,取反操作
  5. 左移(<<):一元运算符,按位左移一定的位置。高位溢出,低位补符号位,符号位不变。
  6. 右移(>>):一元运算符,按位右移一定的位置。高位补符号位,符号位不变,低位溢出。
  7. 无符号右移(>>>):一元运算符,符号位(即最高位)保留,其它位置向右移动,高位补零,低位溢出。

认识异或运算:

  1. 满足结合律 (a ^ b)^ c = a ^ (b ^ c)
  2. 满足交换律 a ^ b = b ^ a
  3. 满足自反性 a ^ a = 0 a ^ b ^ b = a ^ 0=a

2 位运算技巧

  1. 取mid
// 常见写法
int mid = (l + r)/2;
// l+r容易导致溢出
int mid = r + (r - l)/2;
// 位运算优化
int mid = l + ((r - l) >> 1);
  1. N*2+1
// 常见写法 例如 11 = 5 * 2 + 1
int sum = N * 2 + 1;
// 位运算写法 0101 << 1 
// 0 1010舍弃最高位为1010  值为10  
// 1010 | 1 为 1011 为11
int sum =N << 1 | 1;

3 位运算题目

1 如何不用额外变量交换两个数

int a = 3;
int b = 5;
// 通常swap方法 这种方法需要多申请一个变量  增加了常数项的空间复杂度
int temp = a;
a = b;
b = temp;
// 通过位运算 不用开辟额外空间,并且计算速度更快 
a = a^b;
b = a^b;
a = a^b;

2 一个数组中有一组数出现了奇数次,其他出现偶数次,找到并打印这种数

// 方法一 最暴力的使用map 比较简单  不进行演示
// 缺点:需要开辟额外空间map
// 方法二 使用位运算
	    int eor = 0;
        for (int i = 0; i < ints.length; i++) {
            eor ^= ints[i];
        }
        System.out.println(eor);

3 把一个int值的最右侧1提取出来

// 0111
int a = 7; 
/**
 ~a 取反 0111 取反 1000 
 ~a +1  1001
 a & (~a + 1)  0001
*/
int b = a & (~a + 1)

4一个数组中有两个数出现了奇数次,其他出现偶数次,找到并打印这两个数

// 第一种方法: 使用map计数 比较简单 不进行演示
// 缺点: 需要额外开辟空间map
// 第二种方法:通过位运算
        int a = 0;
        for (int i = 0; i < ints.length; i++) {
            // 结果为 a ^ b   因为a,b不为0 所以a^b != 0
            a ^= ints[i];
        }
        int var2 = a & (~a + 1);
        int b = 0;
        for (int i = 0; i < ints.length; i++) {
            if ((var2 & ints[i]) == 0) {
                b ^= ints[i];
            }
        }
        System.out.println(b + "-" + (b ^ a));

二 队列和栈

1 题目

1 实现一个特殊的栈,在基础的功能上实现,实现返回栈中最小数据的功能。

  1. 要求push,pop,getMin的时间复杂度为O(1)

第一种方案:
在这里插入图片描述

两个栈,一个栈为正常存储数据的栈,另一个栈为存储最小数据的栈,每次入栈的时候都要和minData最顶的数据(即栈中最小的数据)进行对比,若新的数据小于栈顶的数据,则压入新的数据进入minData的栈;否则再次压入minData中最小的数据。

优点:栈顶一直记录的是最小的元素,且获取最小的元素的时间复杂度为O(1)

缺点:浪费空间,要创建和data栈一样的栈。

第二种方案:

还是第一种方案的模型,只不过minData栈只会压入新元素小于等于minData栈顶元素的元素。
在这里插入图片描述

2 如何用栈结构实现队列结构

使用两个栈,一个push栈,一个pop栈,push的时候讲数据压入push栈,poll的时候将pop栈中的数据弹出,pop栈的数据从push栈获取。注意事项:

  1. pop栈空时才能从push栈里获取数据
  2. pop从push栈中获取数据时,需要将push栈中的数据全部压入pop栈中。

3 如何用队列结构实现栈结构

使用两个队列,一个push队列,一个pop队列,push的时候把数据往push队列里插入数据,pop的时候将push的队列后面n-1个数据出站并且进入pop队列,然后push和pop队列互换身份,在将pop队列的数据pop出。用两个队列实现了栈的基本操作push和pop操作。
在这里插入图片描述

三 链表相关算法

1 模型

public class Node {
    public int value;

    public Node next;
}

2 题目

链表奇数长度返回中点,偶数长度返回上中点

利用快慢指针,慢指针一次前进一个节点,快指针一次前进两个节点。

    /**
     * 链表奇数长度返回中点,偶数长度返回上中点
     *
     * @param node1
     * @return
     */
    private static Node getMidUp(Node node1) {
        Node fast = node1;
        Node slow = node1;
        while (fast != null && fast.next != null && fast.next.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        return slow;
    }

链表奇数长度返回中点,偶数长度返回下中点

    /**
     * 链表奇数长度返回中点,偶数长度返回下中点
     *
     * @param node1
     * @return
     */
    private static Node getMidDown(Node node1) {
        Node fast = node1;
        Node slow = node1;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        return slow;
    }

判断一个链表是否有环

    /**
     * 判断一个链表是否有环,有环则返回一个入环节点
     * 简单实现,利用set
     *
     * 注意事项:
     * 不建议Node类重写Equels和HashCode,因为有环的情况下会导致栈溢出
     * @param head
     * @return
     */
    public static Node isRing(Node head) {
        HashSet<Node> nodesSet = new HashSet<>();
        Node cur = head;
        while (cur != null) {
            boolean contains = nodesSet.contains(cur);
            if (contains) {
                return cur;
            }
            nodesSet.add(cur);
            cur = cur.next;
        }
        return null;
    }

    /**
     * 判断一个链表是否有环,有环则返回一个入环节点
     * 用快指针和慢指针
     *
     * @param head
     * @return
     */
    public static Node isRingP(Node head) {
        if (head == null || head.next == null) {
            return null;
        }
        Node fast = head;
        Node slow = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) {
                fast = head;
                break;
            }
        }
        if (fast == head) {
            while (fast != slow) {
                fast = fast.next;
                slow = slow.next;
            }
            return slow;
        }
        return null;
    }

给定两个可能有环也可能无环的单链表,头结点为head1和head2。实现一个函数若两个链表相交则返回相交的第一个节点,若不相交则返回null;两链表长度为n,要求时间复杂度为O(n),额外空间复杂度为O(1)。

解题思路:

① 两个都没有环的情况下,可能会存在交叉也可能没有

② 一个有环一个无环的情况下,不可能相交

③ 两个都有环的情况下,可能相交,也可能不相交

​ ① 两个都有环,但不相交

​ ② 在入环节点之前相交

​ ③ 在入环节点之后相交

    /**
     * 两个无环链表判断是否相交,相交则返回相交点,不相交则返回null
     *
     * @param head1
     * @param head2
     * @return
     */
    private static Node bothNoRing(Node head1, Node head2) {
        if (head1 == null || head2 == null) {
            return null;
        }
        Node cur1 = head1;
        Node cur2 = head2;
        int n = 0;
        while (cur1.next != null) {
            cur1 = cur1.next;
            n++;
        }
        while (cur2.next != null) {
            cur2 = cur2.next;
            n--;
        }
        // 最后一个节点不是公共节点,则不是相交
        if (cur1 != cur2) {
            return null;
        }
        // 长的位cur1
        cur1 = n > 0 ? head1 : head2;
        // 段的是cur2
        cur2 = cur1 == head1 ? head2 : head1;
        n = Math.abs(n);
        while (n != 0) {
            n--;
            cur1 = cur1.next;
        }
        while (cur1 != cur2) {
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        return cur1;
    }
/**
     * 判断两个链表是否是有相交
     * ① 两个都没有环的情况下,可能会存在交叉也可能没有
     * ② 一个有环一个无环的情况下,不可能相交
     * ③ 两个都有环的情况下,可能相交,也可能不相交
     *    ① 两个都有环,但不相交
     *    ② 在入环节点之前相交
     *    ③ 在入环节点之后相交
     *
     * @param head1
     * @param head2
     * @return
     */
    public static Node isIntersect(Node head1, Node head2) {
        // 先看是否都有环
        Node ringP1 = isRingP(head1);
        Node ringP2 = isRingP(head2);
        /**一个有环一个没环,那就不可能相交*/
        if (ringP1 == null && ringP2 != null || ringP1 != null && ringP2 == null) {
            return null;
        }
        /**先看两个都无环的情况*/
        if (ringP1 == null && ringP2 == null) {
            return bothNoRing(head1, head2);
        }
        /**两个都有环*/
        return bothRing(head1, ringP1, head2, ringP2);
    }

    /**
     * 两个链表都有环的情况下,有交点则返回交点,无相交则返回null
     *
     * @param head1
     * @param ringP1
     * @param head2
     * @param ringP2
     * @return
     */
    private static Node bothRing(Node head1, Node ringP1, Node head2, Node ringP2) {
        // 在入环节点之前是否相交
        if (ringP1 == ringP2) {
            // 说明在入环节点之前相交了 退化成无环链表的交点问题
            return bothNoRing(head1, head2, ringP1);
        } else {
            // 在入环节点之后相交
            return intersectAfterRing(ringP1, ringP2);
        }
    }

    /**
     * 在入环节点之后相交
     *
     * @param ringP1
     * @param ringP2
     * @return
     */
    private static Node intersectAfterRing(Node ringP1, Node ringP2) {
        Node cur1 = ringP1.next;
        while (cur1 != ringP1) {
            if (cur1 == ringP2) {
                return cur1;
            }
        }
        return null;
    }

    /**
     * 两个无环链表判断是否相交,相交则返回相交点,不相交则返回null
     *
     * @param head1
     * @param head2
     * @return
     */
    private static Node bothNoRing(Node head1, Node head2, Node tail) {
        if (head1 == null || head2 == null) {
            return null;
        }
        Node cur1 = head1;
        Node cur2 = head2;
        int n = 0;
        while (cur1 != tail) {
            cur1 = cur1.next;
            n++;
        }
        while (cur2 != tail) {
            cur2 = cur2.next;
            n--;
        }
        // 最后一个节点不是公共节点,则不是相交
        if (cur1 != cur2) {
            return null;
        }
        // 长的位cur1
        cur1 = n > 0 ? head1 : head2;
        // 段的是cur2
        cur2 = cur1 == head1 ? head2 : head1;
        n = Math.abs(n);
        while (n != 0) {
            n--;
            cur1 = cur1.next;
        }
        while (cur1 != cur2) {
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        return cur1;
    }
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值