围追堵截“七十二”关
🌠

围追堵截“七十二”关

Property
notion image
围追堵截,或者说是查漏补缺,是将自己的面试问或者实际工作用到的一些小知识点,整理后记录下来。

1. 来自互联网的资料

在之前的代码中,无论是Python还是Java,基本在判断值相等的时候都用了“==”;但是也有刷到Java中一个判断相等的函数,.equals();但哪一个是值相等,哪一个是对象相等,今天仔细地探究一下。

1.1 Python篇 - is or ==

该文章指出,Pythonis代表判断对象的身份id,该id是唯一的,可以理解为在比较地址;而==是标准操作符,判断的是值是否相等。
从字面意义上理解,“==”与数学符号中“=”的定义一样为相等,不难理解是值相等;采用自然语言的is判等,是判别身份,也挺好理解。

1.2 Java篇 - .equals() or ==

该文章的观点与Python中的基本一致,即,==是标准操作符,与数学=符号意义相同,.equals()表示判断对象是否一致;

2. 大胆求证一番

2.1 Python求证

首先是Python==Python中的is
notion image
测试环境,Python3.8,所以其实都是在判值相等?当然不是。
notion image
可以看到,如果说is是判断值相等,那么这里不应该出现False,这说明Python对常量,如3,不另外分配空间,而是直接指向一个地址;
最后是部分文章(非前面的链接)有关Python的判等中提到,六大基本类型中==判断值相等,其他对象中,判断id,这其实是因为(自己定义的类)对象没法取某一个值来判断,只能对比id
Python中常用到 is 的时候都是在与None结合,None是常量,Python不会额外分配内存。

2.2 Java求证

首先是Java中的==Java中的.equals()
notion image
看到了这里,为我之前把==.equals()记错汗颜,不知道是哪一篇文章出错了还是真的我记错了,当时的我觉得Java中这一点很反常,所以记得很清楚,结果还记错了。

3. 为什么要区分

可能平常业务中不太用得到对象判等,这个区别(或者是对象判等这个方法)也就容易被忽略。这倒是可以帮我快速判断,函数中参数到底是引用传递还是参数传递(Java没有引用传递,但有时候无需返回又可以修改传入的参数,主要是数组,数组传入的是首地址,但Java的数组不是一个对象?)。
另外,使用对象判等(效果相同的前提下)在高频操作中比值相等高效一点?

4. 什么是线性规划?

待补充SVM模型中目标函数 与 约束条件。
说起来好笑,明明SVM中就有用到线性规划相关的知识,用了目标函数跟约束函数,并且还用到了拉格朗日乘子法进行简化转换,自己却回答说不知道(没想到会直白地问运筹优化,然后对应不上什么是线性规划)。这说明基础知识还是不够扎实,所以下面会对机器学习中涉及到的统计学知识进行一次回顾。
所以,什么是线性规划?
本科数学课有学过线性规划三要素,目标函数、约束条件、问题变量。以SVM举例,其中求解最大间隔为目标函数,约束条件为线性分隔函数需要满足的条件(最大间隔),问题变量为待求解的线性函数参数。
线性规划中常用到原问题的对偶问题来求解,在SVM中使用了拉格朗日乘子法,将约束条件与目标函数放在一起(这样方便自动更新参数,变成损失函数):

5. 线性规划该怎么求解?

在机器学习中,大多使用导数来更新参数值,逼近全局最优解,算是实践解法。常用的理论解法,记得有单纯形法,基本的步骤(贴一篇自己找到的详解,记录一下常用的步骤)是:
首先,将线性规划化为标准型。什么是标准型?标准型是指,约束条件的所有符号都为等号,目标值函数为max,决策变量取值范围非负。
但实际情况中,大多时候都是不等号,这就需要引入松弛变量将不等式化为等式。目标函数为min也只需要取反即可转为max,决策变量也可以通过变量代换,将取值可为负的决策变量取为正(实际问题中决策变量为非负的情况很多见)。
其次,约束条件的个数为基变量的个数,不停求解基变量。假设存在m个约束条件,n个决策变量(n大于等于m),此时可以确定m个基变量,通过行列式的线性变换,将基变量用非基变量表示(目标函数),当基变量为0时,可以得到一个定点值。非基变量可以计算出一个,将最大的选为入基变量,逐一计算替换后原有基的最大的变量(比较拗口),作为入基。直到非基变量的都小于等于0。
那么,单纯形法是怎么计算的呢?很简单,将上述的值计算出来放在表里即可,例如一行,选中变量之后,再有一行。
 

6. ACM模式刷题

之前一直用的是leetcode,只要写核心代码即可,但是当做春招的笔试时,发现是ACM模式。还好我之前本科有上过ACM的选修课,虽然算法没学到多少(那个时候只会C),但是输入输出知道是怎么回事。
记录下自己用Java写算法题的模板:
public class Solution{
		public static void main(String[] args){
				Scanner sn = new Scanner(System.in);
				int n = sn.nextInt();
				sn.nextLine();
				int[] nums = new int[n];
				for(int i=0;i<n;i++){
						nums[i] = sn.nextInt();
				}
				sn.close();

				/**
				核心代码部分
				*/
		}

}
大概需要注意的点在于需要在读取完一行数据之后,输入sn.nextLine()来完成换行,另外,保持close()是一个好习惯。
另外,记录下快排跟归并,有时候需要自己写多个数组的排序,有时候需要自己写类根据某个属性的排序,当然,也是为了面试。
// 快速排序,排两个数组
		private void quickSorted(int[] numsX, int[] numsY, int low, int high){

        System.out.println("I'm executing!");

        if(low>=high) return;
        int baseX = numsX[low], baseY = numsY[low];
        int i=low, j=high;
        while(i<j){

            while(numsX[j]>=baseX && i<j){
                j--;
            }

            while(numsX[i]<=baseX && i<j){
                i++;
            }

            if(i<j){

                int tempValueX = numsX[i], tempValueY = numsY[i];

                numsX[i] = numsX[j];
                numsX[j] = tempValueX;

                numsY[i] = numsX[j];
                numsY[j] = tempValueY;

            }
        }

        numsX[low] = numsX[i];
        numsX[i] = baseX;

        numsY[low] = numsY[i];
        numsY[i] = baseY;

        quickSorted(numsX, numsY, low, i-1);
        quickSorted(numsX, numsY, i+1, high);
    }
这是从网上摘抄的一位大佬写的代码,很漂亮,我自己的话更加习惯下面那种分为两个函数的写法:
// 归并排序,比快排稳定,平均复杂度一致
public static int[] mergeSort(int[] nums, int l, int h) {

        if (l == h)
            return new int[] { nums[l] };

        int mid = l + (h - l) / 2;
        int[] leftArr = mergeSort(nums, l, mid); //左有序数组
        int[] rightArr = mergeSort(nums, mid + 1, h); //右有序数组
        int[] newNum = new int[leftArr.length + rightArr.length]; //新有序数组

        int m = 0, i = 0, j = 0;
        while (i < leftArr.length && j < rightArr.length) {
            newNum[m++] = leftArr[i] < rightArr[j] ? leftArr[i++] : rightArr[j++];
        }
        while (i < leftArr.length)
            newNum[m++] = leftArr[i++];
        while (j < rightArr.length)
            newNum[m++] = rightArr[j++];
        return newNum;
    }
 
private int[] merge(int[] left, int[] right){
        // 结果数组
        int i=0;
        int[] result = new int[left.length + right.length];
        // 循环获取两个数组之间最小的数值
        while(left.length>0 && right.length>0){
            if(left[0] <= right[0]){
                result[i++] = left[0];
                left = Arrays.copyOfRange(left, 1, left.length);
            }else{
                result[i++] = right[0];
                right = Arrays.copyOfRange(right, 1, right.length);
            }
        }
        // 填充剩下的所有数据
        while(left.length>0){
            result[i++] = left[0];
            left = Arrays.copyOfRange(left, 1, left.length);
        }
        while(right.length>0){
            result[i++] = right[0];
            right = Arrays.copyOfRange(right, 1, right.length);
        }
        // 返回结果
        return result;
    }
 

后续

在这里记录一些杂乱的知识点确实很不错,但是目前来说太长了,可以考虑转到一个新的界面,按照类别去记录(也可以加快一点加载速度)。