Leetcode(105)

这个题我看到网上有一个很优美的解法,是利用static variable来做的,因为preorder的第一个元素一定是子树的根,同时,下一个元素一定是左子树的根。以此类推。接下来我们在inorder中寻找这个元素对应的位置,那么从start到这个点是左子树,从这个点到end是右子树。这是一个递归的方法。

这个做法非常好,唯一而且是致命的缺点是static变量只能初始化一次,所以这个函数只能运行一次。很尴尬。正确答案如下:

TreeNode *buildTree(vector<int> &preorder, vector<int> &inorder) {
    return create(preorder, inorder, 0, preorder.size() - 1, 0, inorder.size() - 1);
}

TreeNode* create(vector<int>& preorder, vector<int>& inorder, int ps, int pe, int is, int ie){
    if(ps > pe){
        return nullptr;
    }
    TreeNode* node = new TreeNode(preorder[ps]);
    int pos;
    for(int i = is; i <= ie; i++){
        if(inorder[i] == node->val){
            pos = i;
            break;
        }
    }
    node->left = create(preorder, inorder, ps + 1, ps + pos - is, is, pos - 1);
    node->right = create(preorder, inorder, pe - ie + pos + 1, pe, pos + 1, ie);
    return node;
}

Let me explain the coordinates in the recursion. Very simply, we can see that the inorder traversal is divided into two parts, [is, pos-1] and [pos+1, ie] according to the root node pointed by pos.The first part contains pos – is elements, and the second part has ie- (pos +1)+1 = ie – pos elements.
Correspondingly, in preorder traversal, the elements in the [ps+1, ps+pos – is] intervals belong to the left subtree, and the elements in the [pe – (ie – pos)+1, pe] interval belong to the right subtree.

Leetcode(83)

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        ListNode* curr=head;
        while(curr && curr->next){
            if(curr->val==curr->next->val )
                curr->next=curr->next->next;
            else{
                curr=curr->next;
            }
            
        }
        return head;
    }
};

这个题目的答案不用多讲,答案不言自明。但是我在做这个题目的时候犯了一个错误,curr=curr->next这个不是每次都要更新的。只有当找不到重复的时候我们才再去更新一次。这个其实你也想到过,你当时定义了一个dhead指针,但很复杂,不简洁。

Leetcode(2)

这个题,简而言之就是做一个加法,但是加法的数字不是存储为int格式,而是以linked list的形式储存。同时它返回也要求是一个linked list.所以这个题有两个难点,一是设计出加法计算的算法,二是如何使用构造函数构造出一个新的linked list.关于列表的构造,其实我一直不是很熟练,主要是这个结构有些抽象,而且这个涉及类的构造。说实话我一直很晕。

下面我们先看看答案:

Definition for singly-linked list.

struct ListNode {

int val;

ListNode *next;

ListNode(int x) : val(x), next(NULL) {}

};
/ class Solution { public: ListNode addTwoNumbers(ListNode* l1, ListNode* l2) {
if (l1 == NULL and l2 == NULL) return NULL;
else if (l1 == NULL) return l2;
else if (l2 == NULL) return l1;

int a = l1->val + l2->val;

ListNode *p = new ListNode(a % 10);

p->next = addTwoNumbers(l1->next,l2->next);

if (a >= 10) p->next = addTwoNumbers(p->next, new ListNode(1));

return p; }

1.题目给的节点类的设计要记住,后面会围绕这个进行构造。

2.本题使用的是递归的形式,所以要将基类情况进行说明。

3. 构造节点有两种形式 Node* p = new Node(val),这里使用的是指针的构造,p是指向Node结构的指针。Node p= Node(val)。但是我们要使用->,所以我们选择了第一种。

4.最后一行是因为如果超过了10 要加一

随笔(5)—谈谈binary search

binary search是面试中非常经常被问到的一个题。应该这么说binary search是一个比较简单的算法,但是变形很多,很容易掉到陷阱里面出不来。

废话不说,先看两个题:

这个题应该首先向面试官提问有没有重复

这是我去年面试morgan stanley中遇到的一个题目。诚然对于搜索,最次最次有O(n)复杂度的算法,但一般我们都希望能找到复杂度为$O(logn)$的算法。这个题tricky的点在于他做了一次rotate,如果没有这个rotate,对于一个sorted的数列,直接无脑使用binary search就行了。可是它翻转了,我们应该怎么做呢?

一个自然的想法是如果我们能找到pivot point,那么在pivot point左右两边序列都是sorted的,那么我们在两边分别使用binary search就行了,问题是find_pivot这个算法不能太复杂,最好复杂度也是O(logn),否则这个改进就没有意义了。下面我们看下答案:

 int binary_search(vector<int>& nums,int target,int l,int r){
    int mid=(l+r)/2;
    if(l>r)
        return -1;
    if(nums[mid]==target)
        return mid;
    else if(nums[mid]>target)
        return binary_search(nums,target,l,mid-1);
    else
        return binary_search(nums,target,mid+1,r);
}

int find_pivot(vector<int>& nums){
    int l=0;

    int r=nums.size()-1;
    int mid;

    if(nums[0]<nums[nums.size()-1])
        return 0;

    while(l<r){
        mid=(l+r)/2;
        if(nums[mid]>=nums[0]){
            l=mid+1;
        }
        else{
            r=mid-1;
        }
    }
    return l;
}

int search(vector<int>& nums, int target) {
    if(nums.size()==0)
        return -1;


    int pivot=this->find_pivot(nums);
    int idx1=this->binary_search(nums,target,0,pivot);
    int idx2=this->binary_search(nums,target,pivot,nums.size()-1);
    if(idx1==-1 && idx2==-1)
        return -1;

    if(idx1 !=-1){
        return idx1;
    }

    if(idx2 !=-1){
        return idx2;
    }

    return -1;

}

基本就是按照我们的思路写下来的,难点是如何写binary search.一般来讲binary search 由三个部分构成:

  • 定义中间点mid
  • 定义左起点和右起点 l和r,以及更新方式,一般地l或者r都要个加一或者减一
  • 定义终止循环条件,一般有两种 while(l<r),第二种是while(l<=r), 另一种是while(l<r),具体选择使用那一种主要看l==r这个条件我们是不是要退出,如果要,就将条件设置成l<r。同时要注意,最后返回的时候注意l=mid+1,最后返回l就行。

另外有一点要说明的是,binary_search一般有两种实现方式,一种是使用循环,另一种是使用递归。我们一般都选择使用循环来实现,因为递归实在是too subtle了。当然,如果是vallina形式的binary search可以选择使用递归。

这里要说明的是这样一行代码if(nums[mid]>=nums[0])。请注意,这里是大于等于不是大于。这里我也没有想明白为什么。事实上,关于这些边界问题,我真的没有统一答案,只能一个一个试错了。

随笔(3)–Leetcode 53

废话不说,直接看题目

这个题目属于非常常见的一类,这是因为在实际中我们要求连续最大盈利。当然,这种题也是有暴力解法的,就是任选两个数字,计算\Sigma_{k=i}^{j}A[k],然后在得出最大值就可以了,这样做的复杂度是O(n^2)。既然这样,我们不禁要问,能不能扫描一次就得出结果呢?

对于一个index i,我们定义S[i]=\Sigma_{k=}^{i}A[i],就是前i项的和,那么对于每个i,S[i]是固定的,接下来我们需要确定起点j从而最大化\Sigma_{k=j}^{i}A[k],也就是说,如果我们最小化S[j],那么S[i]-S[j]就是以i为终点的max subarray sum。我们遍历i取出最大值就可以了。代码如下:

注意这里有一个陷阱,两个if条件是不能颠倒的,这里contagious的定义是至少要选一个元素,不能一个都不选,如果两个if条件颠倒了,出现[-1,-1,-1]这种数列,结果会输出零,这是不对的,需要注意

随笔(2)–Leetcode 121

老规矩,废话不说,先看题目吧

简单来讲,给你一个股票序列,交易规则是只能购买一次(先买后卖),求你能获得的最大利润。明眼人看得出来就是找到这个序列的一个波峰和一个波谷,然后再相减。其实这个模型在实际中有很多应用,比如说求解maxmium drawdown.所以是很有可能被问到的。

先说一个最直观的解法,复杂度O(n^2),就是任意选一个序列的两个点,遍历所有的可能,然后选择出最大值。但是我们希望一遍扫描后就能获得结果,那么这就需要思考一下。

一个直觉的想法是,一个序列有很多波峰(局部最大值)和波谷(局部最小值),一个成功的交易策略一定是在一个局部最小值开仓,在一个局部最大值平仓。但是由于我们只能先买后卖,所以波谷要出现在波峰前面。那么我们可以定一个min_price,它会动态的记录所有的局部最小值。然后我们定义一个max_profit,它会反复更新每一个在最小值处开仓的当前最大利润,遍历完后即为全局最大利润。代码如下

class Solution:
def maxProfit(self, prices: List[int]) -> int:
max_profit=
0

    min_price=10000000
    for i in range(len(prices)):
        if prices[i]<min_price:
            min_price=prices[i]

        if (prices[i]-min_price)>max_profit:
            max_profit=prices[i]-min_price


    return max_profit

随笔(1)—Leetcode 76

废话不说,直接看题:

题目非常简单,给定一个distinct的列表,求它的power set。

先说一些题外话,这个函数实际上应用非常广,比如在machine learning中,如果我们需要做feature selection,就是从feature universe里面选出一个子集来提高validation set的准确率,我们就需要这个函数生成feature power set。当然这样做的复杂度比较高,是O( 2^{N})。当然也可以选择forward selection,这样可以把复杂度降低到O(N^2)

所以我翻看了一下我的ML的课堂笔记,看看老师是怎么实现这个函数的:

def subsets(features):
if len(features) == 0:
return []
cs = subsets(features[1:])
return [[features[0],]] + [[features[0],]+ c for c in cs]+ cs

哈哈,乍一看根本不知道写的是个啥,不急,我们先来对python的基本语法做一些说明:

  • list的相加:python的语法里面是可以进行list的加法运算的,其效果就是把两个list的元素合并为一个list. e.g: [3]+[5]=[3,5]; []+[3]=[3]; 3+[5]=error!

所以直观上来看这个函数显而易见,首先cs求出这个列表第2至n项所生成的power set,再把第一个元素加上就行了,[[features[0]]]表示第一个元素生成的subset,[[features[0],]+ c for c in cs]表示第一个元素与cs合并生成的子集,cs表示剔除第一个元素后,别的元素生成的子集。

但是上面的代码有一点问题,他忽略的空集,修改后的代码如下所示:

def subsets(features):
if len(features) == 0:
return [[]]
cs = subsets(features[1:])
return [[features[0]]+ c for c in cs]+ cs

当然上面那个代码也是有实际意义的,因为在machine learning中我们是不会对空集进行测试的。

先提纲挈领的说一下,这种题目在面试中经常考,与之相关的有排列,组合等问题,他们自身的复杂度本身就是指数级别的,所以不能从时间复杂度上进行优化,一般出这种题目是考察你的算法能力。解决这种题递归(recursion)和回溯(backtracking)是最常用的方法。下面对一些变形进行一些说明

2.上面题目不变,请选择出长度为k的子集(Two sigma onsite)

这个题是一道典型的组合问题,组合问题一般地解法就是回溯法(backtrack)。废话不说,先看答案

def subsets(nums):
def backtrack(first = 0, curr = []):
# if the combination is done
if len(curr) == k:
output.append(curr[:])
for i in range(first, n):
# add nums[i] into the current combination
curr.append(nums[i])
# use next integers to complete the combination
backtrack(i + 1, curr)
# backtrack
curr.pop()

output = []
n = len(nums)
for m in range(k,k + 1):
backtrack()
return output

回溯法的基本做法就是一个递归函数,一个判断是否为可行解,在这里可行解的判断条件是长度是否为k.实话说,递归函数看的人眼花缭乱,这里我们的做法就是从全局来看,比方说

if len(curr) == k:
output.append(curr[:])

这个代码就是判断解是否满足条件,如果满足条件就把他放到最终的解空间里面。

for i in range(first, n):
# add nums[i] into the current combination
curr.append(nums[i])
# use next integers to complete the combination
backtrack(i + 1, curr)
# backtrack
curr.pop()

这个是回溯函数的主体,第一行curr.append(nums[i])是把一个新元素放到列表里面,backtrack(i + 1, curr)是把从第i+1个到最后一个元素与第一个元素结合生成的所有可行解,curr.pop()将第一个元素剔除,然后进入下一个循环(因为以第一个元素构成的排列已经枚举完了)

3.排列问题(Permutation)

Given a list composed of distinct integers, generate its all permutations.

这个题使用递归就可以完美解决,下面先看答案

def permute(nums):
  
if len(nums) == 1:
        return [nums]

    result = []
    for i in range(len(nums)):
        nums[0], nums[i] = nums[i], nums[0]
        B = self.permute(nums[1:])
        result = result+[ele+[nums[0]] for ele in B]

    return result

本质和组合一样,也是使用递归。唯一的区别是因为排列是有顺序的,所以第一个元素可以由很多元素来担当,所以这个循环很关键,来让所有元素都当首元素。

R-Squared vs Correlation

1.R-Squared:

The coefficient of determination, denoted R2 or r2 and pronounced “R squared”, is the proportion of the variance in the dependent variable that is predictable from the independent variable(s).

It is a stastical used in the context of statistical models whose main purpose is either the prediction of future outcomes or the testing of  hypothetes, on the basis of other related information. It provides a measure of how well observed outcomes are replicated by the model, based on the proportion of total variation of outcomes explained by the model.

There are several definitions of R2 that are only sometimes equivalent. One class of such cases includes that of simple linear regression where r2 is used instead of R2. When an intercept is included, then r2 is simply the square of the sample correlation coefficient (i.e., r) between the observed outcomes and the observed predictor values. If additional regressors are included, R2 is the square of the coefficient of multiple correlation. In both such cases, the coefficient of determination normally ranges from 0 to 1.

The most general definition of the coefficient of determination is: SST = SSR+SSE

{\displaystyle R^{2}\equiv 1-{SS_{\rm {res}} \over SS_{\rm {tot}}}\,}

2.Correlation

Pearson’s correlation coefficient is the  covariance of the two variables divided by the product of their standard deviations. The form of the definition involves a “product moment”, that is, the mean (the first moment about the origin) of the product of the mean-adjusted random variables; hence the modifier product-moment in the name.

Pearson’s correlation coefficient when applied to a population is commonly represented by the Greek letter ρ (rho) and may be referred to as the population correlation coefficient or the population Pearson correlation coefficient. Given a pair of random variables {\displaystyle (X,Y)}, the formula for ρ[7] is:

{\rho _{X,Y}={\frac {{cov} (X,Y)}{\sigma_{X}\sigma _{Y}}}}    (Eq.1)

where:

纽约生存攻略(信用卡篇)

不知不觉也来纽约也快一年了,经历了很多,遇到了很多。发现美国和中国真的有太多不一样的地方,金融是其中之一。大家来这边一般会开一个信用卡,但其实美国信用卡种类很多,活动也很多,仔细找找有很多发现呢!

SSN

SSN是社保账号,对外国人来说,只有从事付薪水的工作,社保局才会给你发一个社保账号,开始积累信用记录。所以,对于留学生而言,只有博士生才会一开始就有SSN,硕士生,本科生一般是没有的。美国绝大多数的信用卡申请都需要SSN,通过SSN就能把你这一个人在美国从事的所有信用活动关联起来,比如你有没有高额负债或者欠款记录。美国是一个非常重视信用的社会,你的信用分越高,你就有机会申请到更高级的,更实惠的信用卡,甚至你的房贷,车贷的利率都比别人低。所以SSN是越快能拿到越好,信用记录越早开始累积越好。那么如何做到这两点呢?

获取SSN的途径     

对于硕士生而言,我们可以通过在学校申请一份兼职工作来获得SSN,这是因为F签证只允许我们从事校内的工作。以哥大为例:

  • 从第二学期开始,新生可以申请TA或者CA,这是一份赚钱非常高,同时workload也相对较低的工作。大家如果觉得自己的GPA还行,可以大胆地向学校,向老师自荐,可以增加成功的几率。     
  • 学校有时候会放出一些工作,比如 notetaker, paid volunteer, 这些会通过邮件通知,如果你没有SSN,记得去申请,同时也能获得一些收入。     
  • 可以去gym问一下有没有家教,因为哥大有很多体育生,哥大会给他们配备文化课导师,辅导他们的专业课,这是一个很好的机会,因为很少人知道有这个途径。     
  • 找到summer intern  

如何积累信用记录

不管你有没有SSN,积累信用记录一般都是去办一张信用卡,对于不同的情况,办卡的种类也不相同第一张信用卡可以有如下的选择:

  • BoA cash reward credit card for college student               

这是一张非常好的无SSN卡,需要去branch办理,办理之前记得在他们家开一个checking account并充个1w刀,这样的话成功的概率会大很多,不充钱基本上是办不出来的。这张卡返现也很给力,可以自选一个类别享受3%的返现,唯一的缺点就是在branch办理享受不到开卡奖励,但是为了后面更好的卡,这些牺牲也是值得的。     

  • Citi thankyou preferred card for college student             

这个是我的第一张信用卡,它也不需要SSN,需要去branch办理,不需要citi的checking账户有很多钱。但是这个卡现在已经绝版下架了哈哈,其实就算它没有绝版,也不建议申请。因为这张卡是citi thankyou point 体系下的一张信用卡,这个体系的卡24个月只能申请一张,所以如果我们申请了这个卡,同一系列的其他卡就无法申请。第二个原因是这张卡的返现是以thankyou point形式发放的,对于新手很难发挥出最大的价值。新手一般选择redeem 以往的消费或者买gift card,但其实这不是最好的方法。综上这张卡就不要申请啦。

  • Discover it credit card for college student             

对于有SSN的同学来说,这是第一张卡唯一的选择!此卡堪称北美神卡,这张卡每个季度都有一类商品5%的返现,可能的类别包括餐厅,亚马逊,uber,超市,可以说涵盖了生活的方方面面,除此之外,其余消费1%的返现,前12个月返现翻倍,真是美滋滋。如果这张卡是别人refer你的话,你们两个都能获得50刀的credit。还有每年如果你的GPA大于3.0你可以获得20刀的奖励,好学生奖励可以持续五年,我觉得真的很不错,所以在我一拿到SSN后毫不犹豫的就申请了。

Discover 申请链接Discover it 50 credits link

  • Chase Freedom Credit Card     

这是我最近申请的一张卡,chase是一家很好的银行,服务非常不错,但他们家有个规定,就是24个月内只能持有5张信用卡,不是chase的也算,这也就是说,如果我们不能在最初的两年申请尽量多的chase卡的话,以后想要申请就很难了,而chase freedom堪称又一个北美神卡,因为第一他的开卡奖励非常丰富,三个月内消费500刀就返现150刀,注意它的返现不是以现金形式发放,而是以UR点数,当然UR点数可以1:1的换成钱,但这并不是最实惠的用法。但对于不喜欢玩点的人来说是足够的。第二chase freedom和discover一样,每个季度有5%的返现,其余消费1%,而且还时不时会有一些coupon奖励,比如星巴克10% off之类的。总而言之,这是一张非常值得长期持有的信用卡。但是chase的信用卡是要SSN的。

Chase Freedom 申请链接:Chase Freedom

  • Chase Sapphire Preferred Credit Cards(CSP)       

它虽然是一个年费卡,年费95刀,开卡奖励是前三个月消费4000刀返现60000 UR points,用的好的话可以获得800-900刀的价值。除此之外,有了这张卡,可以将UR点数换成一些酒店和航空的积分。至于返现,这张卡一般般,吃饭旅游2%,其余1%的返现。没有外汇转换费,提供租车保险,适合旅游的时候用。

Chase CSP 申请链接:Chase CSP

checking和saving的开户奖励

  • Citi Priority Checking and Saving Account 400 Bonus

         这个奖励对于没有SSN的同学来说,需要去branch办理。具体来讲,要在花旗要同时开checking和saving account,并且在30天内存入15000刀的new money,并将这比钱存放60天,之后的90天内有400刀的奖励到账,个人觉得是一个非常好的羊毛,要的数额也不是特别高,就是有一点需要注意,这个priority账户是有月费的,免除月费的方法是保持balance在10000刀以上。另外说一点,非常不建议把大钱放在这种大银行,因为他们的利率很低,不划算,我建议拿开卡奖励的时候把钱放进去,然后开卡奖励到手了就把钱取出来放到利率更高的账户里面去。

  • Chase College Checking Account 100 Bonus

         这个是最容易获得的开卡奖励,也需要去branch办理,保持余额在25刀以上,然后任意消费10笔,可以获得100刀的奖励,不需要拥有SSN! 注意哦不要轻易也把saving account也开了,这样你会错失很多潜在的薅羊毛的机会。