06_面试中的常用算法

判断是否有环

  • 问题:判断一个链表是否有环?
    在这里插入图片描述
  • 解题方法:
    在这里插入图片描述
  • 假设链表的节点数量是n,则该算法的时间复杂度为O(n),除两个指针外没有使用任何额外的存储空间,所以空间复杂度是O(1)。
  • 问题扩展
    • 环长度?在这里插入图片描述
    • 入环长度?在这里插入图片描述
  • 代码
/*链表节点*/
private static class Node{
    int data;
    Node next;
    Node(int data)
    {this.data = data;}
}

/*判断链表是否有环*/
public static boolean isCycle(Node head)
{
    Node p1 = head;
    Node p2 = head;
    while(p2 != null && p2.next != null)
    {
        p1 = p1.next;
        p2 = p2.next.next;
        if(p1 == p2)
        {return true;}
    }
    return false;
}

/*已知链表有环,计算环的长度*/
public static int cycleLength(Node head)
{
    Node p1 = head;
    Node p2 = head;
    int cycleIndex = 0;
    int cyclelength = 0;
    while(true)
    {
        p1 = p1.next;
        p2 = p2.next.next;
        cyclelength++;
        if(p1 == p2)
        {
            if(cycleIndex == 1)
            {
                return cyclelength - 1;
            }
            cycleIndex++;
            cyclelength = 0;
        }
    }
}

/*已知链表有环,计算入环点*/
public static Node inCycleIndex(Node head)
{
    Node p1 = head;
    Node p2 = head;
    int cycleIndex = 0;
    int ret = 0;
    while(true)
    {
        p1 = p1.next;
        if (cycleIndex == 1)
        {
            p2 = p2.next;
        }
        else{
            p2 = p2.next.next;
        }
        ret++;
        if(p1 == p2)
        {
            if(cycleIndex == 1)
            {
                return p1;
            }
            p2 = head;
            cycleIndex++;
            ret = 0;
        }
    }
}

/*测试*/
public static void main(String[] args) throws Exception
{
    Node node1 = new Node(5);
    Node node2 = new Node(3);
    Node node3 = new Node(7);
    Node node4 = new Node(2);
    Node node5 = new Node(6);
    node1.next = node2;
    node2.next = node3;
    node3.next = node4;
    node4.next = node5;
    node5.next = node2;
    System.out.println(isCycle(node1));

}


最小栈的实现

  • 问题:
    在这里插入图片描述
  • 解决步骤:
    在这里插入图片描述
    在这里插入图片描述
  • 这个解法中进栈、出栈、取最小值的时间复杂度都是O(1),最坏情况空间复杂度是O(n)。
  • 代码
private Stack<Integer> mainStack = new Stack<Integer>();
private Stack<Integer> minStack = new Stack<Integer>();

/*入栈操作*/
public void push(int element)
{
    mainStack.push(element);
    if(minStack.empty() || element < minStack.peak())
    {
        minStack.push(element);
    }
}

/*出栈操作*/
public Integer pop()
{
    if(mainStack.peek().equals(minStack.peek()))
    {
        minStack.pop();
    }
    return mainStack.pop();
}

/*获取栈中最小元素*/
punblic int getMin() throws Exception{
    if(mainStack.empty())
    {
        throws new Exception("stack is empty");
    }
    return minStack.peek();
}

/*测试*/
public static void main(String[] args) throws Exception{
    MinStack stack = new MinStack();
    stack.push(4);
    stack.push(9);
    stack.push(7);
    stack.push(3);
    stack.push(8);
    stack.push(5);
    System.out.println(stack.getMin());
    stack.pop();
    stack.pop();
    stack.pop();
    System.out.println(stack.getMin());  
}

求两个数的最大公约数

  • 辗转相除法。 首先计算出a除以b的余数c,如果c为0则最大公约数就是b,如果不为0,则把问题转化成为b和c的最大公约数;然后计算出b除以c的余数d,把问题转化为c和d的最大公约数,依次类推。如果余数为1,则不用往下进行,结果就是1.
  • 改进版的更相减损法
    在这里插入图片描述
  • 代码

/*辗转相除法*/
public static int getGreatestCommonDivisor(int a , int b)
{
    int c = a % b;
    if(c == 0)
    {
        return b;
    }
    else if(c == 1)
    {
        return 1;
    }
    else
    {
        return getGreatestCommonDivisor(b , c);
    }
}

/*改进版本的更相减损术*/
/*这里有两个技巧:1. 判断奇偶:a&1 如果结果是1,则为奇,如果结果是0,则为偶
                 2. 除2和乘2:a>>1表示除以2,整除,a<<1表示乘以2*/
public static int gcd(int a, int b)
{
    if(a == b)
    {
        return a;
    }
    if((a & 1) == 0 && (b & 1) == 0)
    {
        return gcd(a >> 1, b >> 1) << 1;
    }
    else if((a & 1) == 0 && (b & 1) == 1)
    {
        return gcd(a >> 1, b);
    }
    else if((a & 1) == 1 && (b & 1) == 0)
    {
        return gcd(a, b >> 1);
    }
    else
    {
        int big = a > b ? a : b;
        int small = a < b ? a : b;
        return gcd(smal, big - small);   
    }
}



public static void main(String[] args)
{
    System.out.println(getGreatestCommonDivisor(25 , 5));
}
  • 总结:
    • 暴力枚举法:时间复杂度:O(min(a, b)).
    • 辗转相除法:时间复杂度:O(log(max(a, b))),缺点是取模运算性能较差。
    • 更相减损法:避免了取模运算,但是算法性能不稳定,最坏的时间复杂度为O(max(a, b));
    • 更相减损法和移位相结合:不但避免了取模运算,而且算法稳定,时间复杂度为:O(log(max(a, b)))

如何判断一个数是否为2的整数次幂

  • 解题方法发:如果一个数是为2的整数次幂,则他转化为二进制为1000…,将该值减1,再转化为二进制,为111…。将这两个二进制取与,得到的为0.
  • 代码
public static boolean isPowerOf2(int num)
{
    return (num & num - 1) == 0;
}
  • 时间复杂度为O(1),比O(logn)还要低。

无序数组排序后的最大相邻差

  • 问题:无序数组排序后的最大相邻差
  • 解题方法:
    在这里插入图片描述
  • 代码
public static int getMaxSortedDistance(int[] array)
{
    // 1. 得到数列的最大值和最小值
    int max = array[0];
    int min = array[0];
    for(int i = 1; i < array.length; i++)
    {
        if(array[i] > max)
        {
            max = array[i];
        }
        if(array[i] < min)
        {
            min = array[i];
        }
    }
    int d = max - min;
    // 如果 max 和 min 相等,说明数组所有元素都相等,返回0
    if(d == 0)
    {
        return 0;
    }
    // 2. 初始化桶
    int bucketNum = array.length;
    Bucket[] buckets = new Bucket[bucketNum];
    for(int i = 1; i < array.length; i++)
    {
        buckets[i] = new Bucket();
    }
    // 3. 遍历原始数组,确定每个桶的最大和最小值
    for(int i = 1; i < array.length; i++)
    {
        int index = (array[i] - min) * (bucketNum - 1) / d;
        if(buckets[index].min == null || buckets[index].min > array[i])
        {
            buckets[index].min = array[i];
        }
        if(buckets[index].max == null || buckets[index].max < array[i])
        {
            buckets[index].max = array[i];
        }
    }
    // 4. 遍历桶,找到最大差值
    int leftMax = buckets[0].max;
    int maxDistance = 0;
    for(int i = 1; i < buckets.length; i++)
    {
        if(buckets[i].min == null)
        {
            continue;
        }
        if(buckets[i].min - leftMax > maxDistance)
        {
            maxDistance = buckets[i].min - leftMax;
        }
        leftMax = buckets[i].max;
    }
    return maxDistance;
}

private void class Bucket
{
    Integer min;
    Integer max;
}

/*测试*/
public static void main(String[] args)
{
    int[] array = new int[] {2, 6, 3, 4, 5, 10, 9};
    System.out.println(getMaxSortedDistance(array));
}

如何用栈时间队列

  • 解题方法:队列这里只考虑两个操作分别是入队和出队。入队:直接将一个新元素压入到栈A当中,出队:让栈A中所有的元素按照顺序出栈,再按照出栈顺序压入栈B,然后从栈B中出栈就好。
  • 时间复杂度:
    • 入队的时间复杂度是O(1);
    • 出队的时间复杂度:这里涉及到一个新的概念,叫做均摊时间复杂度,需要元素迁移的出队操作只有少数情况,并且不可能连续出现,其后的大多数出队操作都不需要元素迁移,所以把时间均摊到每一次出队操作上面,其时间复杂度是O(1)。
  • 代码
private Stack<Integer> stackA = new Stack<Integer>();
private Stack<Integer> stackB = new Stack<Integer>();

/*入队操作*/
public void enQueue(int element)
{
    stackA.push(element);
}

/*出队操作*/
public Integer deQueue(){
    if(stackB.isEmpty())
    {
        if(stackA.isEmpty())
        {
            return null
        }
        transfer();
    }
    return stackB.pop();
}

/*栈A元素转移到栈B中*/
private void transfer()
{
    while(!stackA.isEmpty()){
        stackB.push(stackA.pop());
    }
}

/*测试*/
 public static void main(String[] args) throws Exception { 
    StackQueue stackQueue = new StackQueue(); 
    stackQueue.enQueue(1); 
    stackQueue.enQueue(2); 
    stackQueue.enQueue(3); 
    System.out.println(stackQueue.deQueue()); 
    System.out.println(stackQueue.deQueue()); 
    stackQueue.enQueue(4); 
    System.out.println(stackQueue.deQueue()); 
    System.out.println(stackQueue.deQueue()); 
}

寻找一个整数所有数组全排列的下一个数

  • 解题方法:字典序算法在这里插入图片描述
  • 时间复杂度:该算法3个步骤每一步的时间复杂度都是O(n),所以整个时间复杂度也是O(n).
  • 代码
public static int[] findNearestNumber(int[] numbers)
{
    // 1. 从后往前查看逆序区域,找到逆序区域的前一位,也就是数组置换的边界
    int index = findTransferPoint(numbers);
    // 如果数字置换边界是0,说明整个数组以及逆序了,无法得到更大的相同数字组成的整数返回null
    if(index == 0)
    {
        return null;
    }
    // 2. 把逆序的前一位和逆序区域中刚刚大于它的数字交换位置
    int[] numbersCopy = Arrays.copyOf(numbers, numbers.length);
    exchangeHead(numbersCopy, index);
    // 3. 把原来逆序的区域转为顺序
    reverse(numbersCopy, index);
    return numbersCopy;
}

private static int findTransferPoint(int[] numbers)
{
    for(int i = numbers.length - 1; i > 0; i--)
    {
        if(numbers[i] > numers[i - 1])
        {
            return i;
        }
    }
    return 0;
}

private static void exchangedHead(int[] numbers, int index)
{
    int head = numbers[index - 1];
    for(int i = numbers.length - 1; i > 0; i--)
    {
        if(head < numbers[i])
        {
            numbers[index - 1] = numbers[i];
            numbers[i] = head;
            break;
        }
    }
}

private static void reverse(int[] num, int index)
{
    for(int i = index, j = num.length - 1; i < j; i++, j--)
    {
        int temp = num[i];
        num[i] = num[j];
        num[j] = temp;
    }
}

pubic static void main(String[] args)
{
    int[] numbers = {1, 2, 3, 4, 5};
    for(int i = 0; i < 10; i++)
    {
        numbers = findNearstNumber(numbers);
        outputNumbers(numbers);
    }
}

private static void outputNumbers(int[] numbers){
    for(int i : numbers)
    {
        System.out.println(i);
    }
}

怎么样寻找删除K个数字后的最小值

  • 问题描述:
    在这里插入图片描述
  • 解决方法:此时,无论删除哪一个数字,最后的结果都是从9位整数变为8位整数。既然同样是8位整数,显然应该优先把高位的数降低,这样对新整数的值影响最大。如果把高位的是最降低呢?把原整数的所有数字从左到右进行比较,如果发现某一位数字大于它右边的数字,那么在删除该数字后,必然会使得该数位的值降低,因为右边比他小的数字顶替他的位置。
  • 时间复杂度:上面的方法只对所有数字遍历了一次,遍历的时间复杂度是O(n),把栈转化为字符串的时间复杂度也是O(n),所以最终的时间复杂度是O(n).
  • 代码
/*删除整数的k个数字,获得删除后的最小值*/
public static String removeKDigists(String num, int k )
{
    // 新整数的最终长度 = 原整数长度 - k
    int newLength = num.length() - k;
    // 创建一个栈,用于接收所有数字
    char[] stack = new char[num.length()];
    int top = 0;
    for(int i = 0; i < num.length(); i++)
    {
        // 遍历当前数字
        char c = num.charAt(i);
        // 当栈顶的元素大于遍历到的当前数字时,栈顶数字出栈(相当于删除数字)
        while(i > 0 && stack[top - 1] > c && k > 0)  // 这个地方比较直接可以用字符串
        {
            top--;
            k--;
        }
        // 遍历到的当前数字入栈
        stack[top++] = c;
    }
    // 找到栈stack中第一个非零数字的位置,以此构建新的整数字符串
    int offset = 0;
    while(offset < newLength && stack[offset] == '0')
    {
        offset++;
    }
    // 返回的时候有截取,这样就解决了如果整数都是从小到大的情况。
    return offset == newLength ? "0" : new String(stack, offset, newLength - offset);
}

/*测试*/
public static void main(String[] args) { 
    System.out.println(removeKDigits("1593212",3)); 
    System.out.println(removeKDigits("30200",1)); 
    System.out.println(removeKDigits("10",2)); 
    System.out.println(removeKDigits("541270936",3)); 
    }

挖金矿

  • 问题描述:
    在这里插入图片描述
  • 解题方法:
    • 这是动态规划,首先对于问题中的金矿来说,每个金矿都存在这挖和不挖两种选择,如果最后一个金矿不挖,则问题变成10个人在前面4个金矿中选择,如果挖,则最后一个金矿消耗3个工人,问题简化成7个工人在前面4个金矿中选择。依次类推,所以:
    • 吧金矿数量设为n,工人数量设为w,金矿的含金量设为数组g[],金矿所需开采人数设为p[],设F(n, w)为n个金矿,w个工人时的最优收益函数,那么状态转移方程是:
      F(n, w) = 0 (n = 0 或者 w = 0) 问题边界:金矿数为0或者工人数为0的情况。
      F(n, w) = max(F(n - 1, w), F(n - 1, w - p[n - 1]) + g[n - 1]) (n > 0, w >= p[n - 1])
  • 表格解决思路:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-diDdRgUD-1611191075515)(/图片资源/挖金矿表格.png )]
    F(1,1) = F(1-1, 1) = F(0, 1) = 0
    F(1,2) = F(1-1, 2) = F(0, 2) = 0
    F(1,3) = F(1-1, 3) = F(0, 3) = 0
    F(1,4) = F(1-1, 4) = F(0, 4) = 0
    F(1,5) = max(F(1-1, 5), F(1-1, 5-5)) = max(F(0, 5), F(0, 0) + 400) = 400
  • 表格法的优化:一般的思路是new一个二维数组,用来装表格,但是这样就开辟了nw的空间,优化就在这上面。在表格的第一行之外,每一行的结果都是由上一行数据推到出来的,以5个金矿9个工人为例,4个金矿、9个工人的最优结果是由它的两个最优子结构,也就是3个金矿、5个工人和3个金矿、9个工人的结果推到出来的,这两个最优子结构都是位于它的上一行。所以在程序中并不需要保存整个表格,无论金矿多少座,我们只保存一行的数据即可,在计算下一行时,要从右向左统计
  • 复杂度:
    • 时间复杂度:O(nw)
    • 空间复杂度:O(n)
  • 代码
/*
获得金矿最优收益
w: 工人数量
p: 金矿开采所需的工人数量
g: 金矿储量
*/
public static int getBestGoldMiningV1(int w, int[] p, int[] g)
{
    int n = g.length;
    // 创建当前结果
    int ret = ne int[w + 1];
    // 填充一维数组
    for(int i=1; i <= n; i++)
    {
        for(int j=w; j > 0; j--)
        {
            if(j >= p[i - 1])
            {
                ret[j] = Math.max(ret[j], ret[j - p[i - 1]] + g[i - 1]);
            }
        }
    }
    // 返回最有一个格子的值
    return ret[w];
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值