[{"content":"","date":null,"permalink":"https://blog.saltfish.org/posts/","section":"Posts","summary":"","title":"Posts"},{"content":"Last time, we finished the first stage of Raft, sucessfully electing a leader without logs. In stage 2, which is the most difficult stage I think, we need to implement the log replication. Leader will receive the command from the client, append it to its log, and then send AppendEntries RPC to the followers. Followers will append the entries to their logs and reply with success or failure.\nVote with logs #In stage 2, we need to consider whether to vote for a candidate based on the logs. A server will vote for a candidate if the candidate\u0026rsquo;s log is at least as up-to-date as its own log, no matter what the term is. Since we have assumed only leader can receive the command from the client, this is a valid strategy. We need to prevent an isolated server with stale logs keep increasing its term, when it finally gets connected to the cluster, it will become the leader because it has the highest term, but its logs are stale, which leads to wrong results.\nLog replication #Whenever the leader receives a command from the client, it will append the command to its log and then send AppendEntries RPC to the followers.\nThe AppendEntries RPC contains\nterm: leader\u0026rsquo;s term leaderId: so followers can redirect clients prevLogIndex and prevLogTerm: to make sure followers have the same log as the leader entries: the log entries to store (empty for heartbeat; may send more than one for efficiency) leaderCommit: leader’s commitIndex, used for apply logs to state machine When a follower receives an AppendEntries RPC, it will first check the term as we did in stage 1, if the term is smaller than the current term, it will reply with false and current term, which will tell the stale leader to step down. If prevLogIndex and prevLogTerm don\u0026rsquo;t match the follower\u0026rsquo;s log, which means there\u0026rsquo;s mismatch between the leader and the follower, the leader will decrease the nextIndex for that follower and retry until it finds the match.\nLeader #For leader, it needs to maintain two arrays, nextIndex and matchIndex. nextIndex[i] is the index of the next log entry to send to follower i, which is initialized to leader\u0026rsquo;s last log index + 1. matchIndex[i] is the index of the highest log entry known to be replicated on follower i, which is initialized to 0. Whenever the leader receives a successful reply from a follower, it will update nextIndex and matchIndex for that follower.\nOnce over half of the followers have replicated a log entry, which can be calculated by matchIndex, the leader can commit that log entry and apply it to the state machine. All followers will receive the commitIndex in the AppendEntries RPC, and they will apply the log entries up to commitIndex to their state machines.\nFollower #What follower should do is quite simple, it just needs to append the entries to its log and reply with success or failure according to the rules we mentioned above.\nImprovements beyond paper #A common issue is when a new follower has no log entries and the leader already has many log entries, the leader decreases the nextIndex for that follower one by one until it finds the match, which is very inefficient. To solve this problem, we have different strategies, one is to use binary search to find the match, which can reduce the time complexity from O(n) to O(log n). Another strategy is to use a hint from the follower\u0026rsquo;s reply, which can tell the leader where the mismatch is, using conflictIndex and conflictTerm in the reply, we will check conflictTerm in the leader\u0026rsquo;s log first, if exists, we will set nextIndex to the last index of that term + 1, if not exists, we will take a look at conflictIndex, which is the index of the first log entry with a term larger than conflictTerm, and set nextIndex to conflictIndex. This can significantly reduce the time to find the match.\n","date":"7 March 2026","permalink":"https://blog.saltfish.org/posts/raft-stage2/","section":"Posts","summary":"\u003cp\u003eLast time, we finished the first stage of Raft, sucessfully electing a leader without logs. In stage 2, which is the most difficult stage I think, we need to implement the log replication. Leader will receive the command from the client, append it to its log, and then send AppendEntries RPC to the followers. Followers will append the entries to their logs and reply with success or failure.\u003c/p\u003e","title":"Raft Stage2"},{"content":"","date":null,"permalink":"https://blog.saltfish.org/","section":"Saltfish's Blog","summary":"","title":"Saltfish's Blog"},{"content":"","date":null,"permalink":"https://blog.saltfish.org/categories/","section":"Categories","summary":"","title":"Categories"},{"content":"Link\nProblem #You are given an integer array nums. We call a subset of nums good if its product can be represented as a product of one or more distinct prime numbers.\nFor example, if nums = [1, 2, 3, 4]:\n[2, 3], [1, 2, 3], and [1, 3] are good subsets with products 6 = 2*3, 6 = 2*3, and 3 = 3 respectively.\n[1, 4] and [4] are not good subsets with products 4 = 2*2 and 4 = 2*2 respectively.\nReturn the number of different good subsets in nums modulo 1e9 + 7.\nA subset of nums is any array that can be obtained by deleting some (possibly none or all) elements from nums. Two subsets are different if and only if the chosen indices to delete are different.\nConstraints #1 \u0026lt;= nums.length \u0026lt;= 1e5\n1 \u0026lt;= nums[i] \u0026lt;= 30\nThoughts #At the first glance, we should notice its a typical pick or not pick problem and the data range is 30, which is a strong hint that we can use bitmask to represent the status of the prime factors.\nSolution #class Solution: def numberOfGoodSubsets(self, nums): P = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] cnt = Counter(nums) bm = [sum(1\u0026lt;\u0026lt;i for i, p in enumerate(P) if x % p == 0) for x in range(31)] bad = set([4, 8, 9, 12, 16, 18, 20, 24, 25, 27, 28]) M = 10**9 + 7 @cache def dp(mask, num): if num == 1: return 1 ans = dp(mask, num - 1) if num not in bad and mask | bm[num] == mask: ans += dp(mask ^ bm[num], num - 1) * cnt[num] return ans % M return ((dp(1023, 30) - 1) * pow(2, cnt[1], M)) % M Explanation # There are only 10 prime numbers less than 30, so we can use a 10-bit bitmask to represent the status of the prime factors. For example, if a number has prime factors 2 and 3, its bitmask will be 0b11 (1\u0026laquo;0 for 2 and 1\u0026laquo;1 for 3). We can use a set to store the bad numbers, which are the numbers that have repeated prime factors. For example, 4 has prime factor 2 repeated twice, so it\u0026rsquo;s a bad number. Since the problem ask for the number of subset, so we don\u0026rsquo;t care about the order of the numbers, we can use a counter to count the frequency. We can use a cached dp function to go over all possible conditions. For each number and each mask, we can either pick or not pick the number. If we pick the number, we need to check if it\u0026rsquo;s a bad number and if it has any common prime factor with the current mask. We can use O(1) time by preprocessing the bitmask for each number. If the results of xor operation is the same as the current mask, it means they have no common prime factor, so we can pick the number and update the mask by xor operation. Finally, we need to multiply the result by 2^cnt[1] because for each 1 we can either pick or not pick it, and 1 doesn\u0026rsquo;t affect the product, so it can be included in any subset without affecting the good property. We start with dp(1023, 30) because 1023 is the bitmask with all 10 bits set to 1, and we start from the largest number 30 and go down to 1. We need to subtract 1 from the result because we don\u0026rsquo;t want to count the empty subset. Finally, we take the result modulo 1e9 + 7 as required by the problem. Insights # Using bitmask to represent the status of the prime factors, actually use a tuple or string to represent the status of the prime factors is also fine, but bitmask is more efficient and easier to implement. ","date":"20 February 2026","permalink":"https://blog.saltfish.org/posts/the-number-of-good-subsets/","section":"Posts","summary":"\u003cp\u003e\u003ca href=\"https://leetcode.com/problems/the-number-of-good-subsets/description/\" target=\"_blank\" rel=\"noreferrer\"\u003eLink\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"problem\" class=\"relative group\"\u003eProblem \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#problem\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003eYou are given an integer array nums. We call a subset of nums good if its product can be represented as a product of one or more distinct prime numbers.\u003c/p\u003e\n\u003cp\u003eFor example, if \u003ccode\u003enums = [1, 2, 3, 4]\u003c/code\u003e:\u003cbr\u003e\n\u003ccode\u003e[2, 3]\u003c/code\u003e, \u003ccode\u003e[1, 2, 3]\u003c/code\u003e, and \u003ccode\u003e[1, 3]\u003c/code\u003e are good subsets with products \u003ccode\u003e6 = 2*3\u003c/code\u003e, \u003ccode\u003e6 = 2*3\u003c/code\u003e, and \u003ccode\u003e3 = 3\u003c/code\u003e respectively.\u003cbr\u003e\n\u003ccode\u003e[1, 4]\u003c/code\u003e and \u003ccode\u003e[4]\u003c/code\u003e are not good subsets with products \u003ccode\u003e4 = 2*2\u003c/code\u003e and \u003ccode\u003e4 = 2*2\u003c/code\u003e respectively.\u003cbr\u003e\nReturn the number of different good subsets in nums modulo 1e9 + 7.\u003c/p\u003e","title":"The Number of Good Subsets"},{"content":"Raft #This time we are following the Raft paper and implementing the first stage of Raft, which is leader election. We will implement the leader election algorithm in a simple way, without any optimizations. We will explain all detailed explanation about raft paper in another posts. Our basic code structure is provided by 6.824. So we will not provide the code here, but we will explain the main idea of the leader election algorithm.\nElection #For each raft cluster, there are at most one leader.\nFor each server, it has 3 states: follower, candidate, and leader.\nFollower # For follower, it will remain follower if it continuously receiving valid heartbeats from the leader. We define a valid heartbeat as a message from server whose term is not smaller than the server\u0026rsquo;s current term. Follower has only one vote each term, so unless its term increased, it will vote for the first candidate that requests its vote. After that it will note voteFor to prevent voting for another candidate in the same term. This ensues that there is at most one leader in the cluster at any time. (At most one server can receive votes from a majority of the servers) Candidate # For candidate, it will transfer from follower if the timer reaches the election timeout and it has not received any valid heartbeats from the leader. When a server becomes a candidate, it increments its current term and votes for itself. Then it sends RequestVote RPCs to all other servers to ask for their votes. Once a candidate receives votes from a majority of the servers, it becomes the leader. If a candidate receives a message from a server with a higher term, it steps down and becomes a follower again. Leader # After election, the leader will send heartbeats to all followers to maintain its leadership. Leader is the only server that can receive client requests and replicate log entries to followers. Notes #Whatever the state of a server, if it receives a message with a term larger than its current term, it updates its current term and steps down to follower. This is to ensure that there is only one leader in the cluster at any time.\nVote # Each Candidate will vote for itself and send RequestVote RPCs to all other servers. Each Follower will vote for the first candidate that requests its vote, and will not vote for any other candidate in the same term. Heartbeat # The leader will send AppendEntries RPCs to all followers to maintain its leadership. The followers will reset their election timeout timer when they receive valid heartbeats from the leader. Some suggestions for implementation: # Whenever increase a server\u0026rsquo;s term, remember to reset voteFor to null, otherwise the server will never vote for any candidate in the future. Also, check this in the very begignning of RequestVote and AppendEntries RPC handlers. Using lock is necessary, more important is if you need to temporarily store the data like slice or map, you need to copy the data to a local variable before using it. Not just use the := operator because they depend on the same underlying data structure.\nMake copy of all temporary variables in the same lock session to ensure the data consistency. For go implementation, defer helps to release the lock. Correctly use time.Timer to implement the election timeout and heartbeat interval. Remember to reset the election timeout timer when receiving valid heartbeats from the leader, otherwise the server will become candidate and start a new election even if there is a healthy leader in the cluster. LOGS ARE IMPORTANT. Raft is a distributed algorithm, it\u0026rsquo;s hard to debug without logs. Especially facing flaky problems, logs can help you reconstruct the history of the cluster and find out the root cause. It\u0026rsquo;s common that some bugs need to be reproduced after 20 times of rerunning the test. ","date":"14 February 2026","permalink":"https://blog.saltfish.org/posts/raft-stage1/","section":"Posts","summary":"\u003ch1 id=\"raft\" class=\"relative group\"\u003eRaft \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#raft\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h1\u003e\u003cp\u003eThis time we are following the \u003ca href=\"https://raft.github.io/raft.pdf\" target=\"_blank\" rel=\"noreferrer\"\u003eRaft paper\u003c/a\u003e and implementing the first stage of Raft, which is leader election. We will implement the leader election algorithm in a simple way, without any optimizations.\nWe will explain all detailed explanation about raft paper in another posts.\nOur basic code structure is provided by 6.824.\nSo we will not provide the code here, but we will explain the main idea of the leader election algorithm.\u003c/p\u003e","title":"Raft Stage1"},{"content":"Link Merge Two Binary Trees\nProblem #You are given two binary trees root1 and root2.\nImagine that when you put one of them to cover the other, some nodes of the two trees are overlapped while the others are not. You need to merge the two trees into a new binary tree. The merge rule is that if two nodes overlap, then sum node values up as the new value of the merged node. Otherwise, the NOT null node will be used as the node of the new tree.\nReturn the merged tree.\nNote: The merging process must start from the root nodes of both trees.\nThoughts #We just need to traverse both trees and return the merge TreeNode to parent node.\nSolution ## class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: def mergeTrees(self, root1: Optional[TreeNode], root2: Optional[TreeNode]) -\u0026gt; Optional[TreeNode]: def dfs(root1,root2): if not root1 and not root2: return val=0 l1,r1,l2,r2=None,None,None,None if root1: val+=root1.val l1=root1.left r1=root1.right if root2: val+=root2.val l2=root2.left r2=root2.right l=dfs(l1,l2) r=dfs(r1,r2) return TreeNode(val,l,r) return dfs(root1,root2) Explanation # We use a simple dfs to go over all tree nodes. We use l1,r1,l2,r2 to store the left and right child of root1 and root2 respectively. We assign value instead of directly use root1 and root2 sometimes root1 or root2 may be None and we can\u0026rsquo;t get the child of None. Insights # Return new nodes will make it easier to handle undecided tree structure. ","date":"14 February 2026","permalink":"https://blog.saltfish.org/posts/merge-two-binary-tree/","section":"Posts","summary":"\u003cp\u003eLink \u003ca href=\"https://leetcode.com/problems/merge-two-binary-trees/description/\" target=\"_blank\" rel=\"noreferrer\"\u003eMerge Two Binary Trees\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"problem\" class=\"relative group\"\u003eProblem \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#problem\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003eYou are given two binary trees root1 and root2.\u003c/p\u003e\n\u003cp\u003eImagine that when you put one of them to cover the other, some nodes of the two trees are overlapped while the others are not. You need to merge the two trees into a new binary tree. The merge rule is that if two nodes overlap, then sum node values up as the new value of the merged node. Otherwise, the NOT null node will be used as the node of the new tree.\u003c/p\u003e","title":"Merge Two Binary Tree"},{"content":"Link: Stone Game\nProblem #Alice and Bob play a game with piles of stones. There are an even number of piles arranged in a row, and each pile has a positive integer number of stones piles[i].\nThe objective of the game is to end with the most stones. The total number of stones across all the piles is odd, so there are no ties.\nAlice and Bob take turns, with Alice starting first. Each turn, a player takes the entire pile of stones either from the beginning or from the end of the row. This continues until there are no more piles left, at which point the person with the most stones wins.\nAssuming Alice and Bob play optimally, return true if Alice wins the game, or false if Bob wins.\nThoughts #Naively, we can solve this problem recursively by simulating the game.\nFor this problem, Alice can decide to take all even indexed piles or all odd indexed piles.\nExample # Initally, we have piles = [5, 3, 4, 5]. If Alice takes the first pile(index 0), Bob can only take odd indexed piles(index 1 or 3). And Alice can keep taking even indexed piles(index 2) until there are no more piles left. If Alice takes the last pile(index 3), Bob can only take even indexed piles(index 0 or 2). And Alice can keep taking odd indexed piles(index 1) until there are no more piles left. Since the total number of stones is odd, Alice will always win the game by taking either all even indexed piles or all odd indexed piles.\nSolution #class Solution: def stoneGame(self, piles: List[int]) -\u0026gt; bool: return True Explanation # Just return True :) ","date":"13 February 2026","permalink":"https://blog.saltfish.org/posts/stone-game/","section":"Posts","summary":"\u003cp\u003eLink: \u003ca href=\"https://leetcode.com/problems/stone-game/\" target=\"_blank\" rel=\"noreferrer\"\u003eStone Game\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"problem\" class=\"relative group\"\u003eProblem \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#problem\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003eAlice and Bob play a game with piles of stones. There are an even number of piles arranged in a row, and each pile has a positive integer number of stones \u003ccode\u003epiles[i]\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eThe objective of the game is to end with the most stones. The total number of stones across all the piles is odd, so there are no ties.\u003c/p\u003e","title":"Stone Game"},{"content":"Our chat system collapsed under 20 concurrent requests\nCurrently, I\u0026rsquo;m working on a RAG(Retrieval Augmented Generation) project, while most of the process is done by calling api, the only computation intensive part is the reranking process, which is a method to rank the retrieved documents based on their relevance to the query. In this step we called BAAI/bge-reranker-v2-m3 model locally.\nAt very begining, we never consider the performance issue until our client ask us to provide a stress test report. I\u0026rsquo;m confident that we have 8 core and 16GB RAM, which is more than enough for the reranking process. When the concurrent request is 20, all cpu resource is drained and the response time is over 20 seconds, which is unacceptable.\nBecause I know it\u0026rsquo;s the only place will consume a lot of resource and deep learning models can be significantly accelerated by GPU, I decide to try to run the reranking process on GPU. We used a NVIDIA T4 GPU with 4 core CPU, which is an entry level GPU, and it easily handle 50 concurrent requests with response time remained stable at 5 seconds . I believe 50 isn\u0026rsquo;t the upper limit, because in case of reached resource saturation, we hardcoded concurrent request to 50. Under 50 concurrent requests, the CPU usage is around 40% and GPU usage is around 10%. It clearly showed the great potential of our system.\nIn the end, I\u0026rsquo;m not sure will our client decide to use GPU for higher QPS, the GPU is 3 times more expensive than CPU, but it can provide 5 times performance improvement. 3 times costs for 10 times performance improvement is a good deal, but it also depends on the actual number of concurrent requests and the budget. If the concurrent request is not high, the improvement might not be necessary.\n","date":"12 February 2026","permalink":"https://blog.saltfish.org/posts/usage-of-entry-level-gpu/","section":"Posts","summary":"\u003cp\u003e\u003cstrong\u003eOur chat system collapsed under 20 concurrent requests\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eCurrently, I\u0026rsquo;m working on a RAG(Retrieval Augmented Generation) project, while most of the process is done by calling api, the only computation intensive part is the reranking process, which is a method to rank the retrieved documents based on their relevance to the query. In this step we called \u003ccode\u003eBAAI/bge-reranker-v2-m3\u003c/code\u003e model locally.\u003c/p\u003e","title":"Usage of Entry Level GPU"},{"content":"","date":null,"permalink":"https://blog.saltfish.org/categories/engineering/","section":"Categories","summary":"","title":"Engineering"},{"content":"","date":null,"permalink":"https://blog.saltfish.org/tags/greedy/","section":"Tags","summary":"","title":"Greedy"},{"content":"","date":null,"permalink":"https://blog.saltfish.org/tags/leetcode/","section":"Tags","summary":"","title":"Leetcode"},{"content":"Problem Link: Split a String in Balanced Strings\nProblem #Balanced strings are those that have an equal quantity of \u0026lsquo;L\u0026rsquo; and \u0026lsquo;R\u0026rsquo; characters.\nGiven a balanced string s, split it into some number of substrings such that:\nEach substring is balanced. Return the maximum number of balanced strings you can obtain.\nExample 1:\nInput: s = \u0026ldquo;RLRRLLRLRL\u0026rdquo;\nOutput: 4\nExplanation: s can be split into \u0026ldquo;RL\u0026rdquo;, \u0026ldquo;RRLL\u0026rdquo;, \u0026ldquo;RL\u0026rdquo;, \u0026ldquo;RL\u0026rdquo;, each substring contains same number of \u0026lsquo;L\u0026rsquo; and \u0026lsquo;R\u0026rsquo;.\nThoughts #Given the input string is balanced, actually we are finding how many positions we can split the string to make sure each substring is balanced. It\u0026rsquo;s easy to think of we can use gready method to solve this problem. As long as we find a balanced substring, we can split it and start to find the next one.\nclass Solution: def balancedStringSplit(self, s: str) -\u0026gt; int: cnt=1 if s[0]==\u0026#39;L\u0026#39; else -1 ans=0 for i in s[1:]: if i==\u0026#39;L\u0026#39;: cnt+=1 else: cnt-=1 if cnt==0: ans+=1 return ans Explanation # We use a counter to count the number of \u0026lsquo;L\u0026rsquo; and \u0026lsquo;R\u0026rsquo; characters We initialize the counter to 1 if the first character is \u0026lsquo;L\u0026rsquo; and -1 if it\u0026rsquo;s \u0026lsquo;R\u0026rsquo; We iterate through the string starting from the second character, updating the counter based on whether we encounter \u0026lsquo;L\u0026rsquo; or \u0026lsquo;R\u0026rsquo; Whenever the counter returns to 0, it means we have found a balanced substring, so we increment our answer ","date":"12 February 2026","permalink":"https://blog.saltfish.org/posts/split_a_string_in_balanced_strings/","section":"Posts","summary":"\u003cp\u003eProblem Link: \u003ca href=\"https://leetcode.com/problems/split-a-string-in-balanced-strings\" target=\"_blank\" rel=\"noreferrer\"\u003eSplit a String in Balanced Strings\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"problem\" class=\"relative group\"\u003eProblem \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#problem\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003eBalanced strings are those that have an equal quantity of \u0026lsquo;L\u0026rsquo; and \u0026lsquo;R\u0026rsquo; characters.\u003c/p\u003e\n\u003cp\u003eGiven a balanced string \u003ccode\u003es\u003c/code\u003e, split it into some number of substrings such that:\u003c/p\u003e\n\u003cp\u003eEach substring is balanced.\nReturn the maximum number of balanced strings you can obtain.\u003c/p\u003e","title":"Split a String in Balanced Strings"},{"content":"","date":null,"permalink":"https://blog.saltfish.org/tags/string/","section":"Tags","summary":"","title":"String"},{"content":"","date":null,"permalink":"https://blog.saltfish.org/tags/","section":"Tags","summary":"","title":"Tags"},{"content":"","date":null,"permalink":"https://blog.saltfish.org/tags/dynamic-programming/","section":"Tags","summary":"","title":"Dynamic Programming"},{"content":"","date":null,"permalink":"https://blog.saltfish.org/tags/game-theory/","section":"Tags","summary":"","title":"Game Theory"},{"content":"Stone Game II #Link: Stone Game II\nProblem #Alice and Bob continue their games with piles of stones. There are a number of piles arranged in a row, and each pile has a positive integer number of stones piles[i]. The objective of the game is to end with the most stones.\nAlice and Bob take turns, with Alice starting first.\nOn each player\u0026rsquo;s turn, that player can take all the stones in the first X remaining piles, where 1 \u0026lt;= X \u0026lt;= 2M. Then, we set M = max(M, X). Initially, M = 1.\nThe game continues until all the stones have been taken.\nAssuming Alice and Bob play optimally, return the maximum number of stones Alice can get.\nThoughts #The problem wants us to maximize the number of stones Alice can get.\nSince 2 player plays optimally, we only care about how much current player can get most, which also means the other player can get least even if he plays optimally.\nSolution #class Solution: def stoneGameII(self, piles: List[int]) -\u0026gt; int: n = len(piles) suffix_sum = [0] * n suffix_sum[-1] = piles[-1] for i in range(n - 2, -1, -1): suffix_sum[i] = suffix_sum[i + 1] + piles[i] # use suffix sum to easily calculate # how many stones remain at each condition @cache def dp(i, m): if i + 2 * m \u0026gt;= n: return suffix_sum[i] # Take all remaining stones max_stones = 0 for x in range(1, 2 * m + 1): max_stones = max(max_stones, suffix_sum[i] - dp(i + x, max(m, x))) return max_stones return dp(0, 1) Explanation # We use suffix sum to speed up calculation The dp`` function returns the maximum number of stones the current player can get starting from index iwithM = m`. Our base case is when we can take all remaining stones, which means we can return the suffix sum at that point We want to go over all the cases we can get at this condition i and m and find out the minium number of stones the other player can get. By subtracting, we got the number of stones the current player can get at this condition. We want to maximize this number, so we update our answer by comparing with the current answer. Start from index 0 and M = 1, we can get the answer. Insights # In game theory, usually we don\u0026rsquo;t care about who is playing, focus on the best strategy is more important. ","date":"11 February 2026","permalink":"https://blog.saltfish.org/posts/stone-game-ii/","section":"Posts","summary":"\u003ch1 id=\"stone-game-ii\" class=\"relative group\"\u003eStone Game II \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#stone-game-ii\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h1\u003e\u003cp\u003eLink: \u003ca href=\"https://leetcode.com/problems/stone-game-ii/\" target=\"_blank\" rel=\"noreferrer\"\u003eStone Game II\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"problem\" class=\"relative group\"\u003eProblem \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#problem\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003eAlice and Bob continue their games with piles of stones. There are a number of piles arranged in a row, and each pile has a positive integer number of stones piles[i]. The objective of the game is to end with the most stones.\u003c/p\u003e","title":"Stone Game II"},{"content":"","date":null,"permalink":"https://blog.saltfish.org/categories/leetcode/","section":"Categories","summary":"","title":"Leetcode"},{"content":"","date":null,"permalink":"https://blog.saltfish.org/tags/ai/","section":"Tags","summary":"","title":"AI"},{"content":"In Microsoft\u0026rsquo;s AI Foundry, it might be a bit tricky to get it work with Langchain at first. What need to be specified is different model provider has different service endpoint and need to use different class to call the API.\nAzure OpenAI #from langchain_openai import AzureChatOpenAI llm = AzureChatOpenAI( azure_deployment=\u0026#34;gpt-5.2-chat\u0026#34;, azure_endpoint=\u0026#34;https://\u0026lt;your-service\u0026gt;.cognitiveservices.azure.com/\u0026#34;, api_version=\u0026#34;2024-05-01-preview\u0026#34;, api_key=\u0026#34;\u0026lt;your-api-key\u0026gt;\u0026#34;, max_tokens=4096, ) api_key use AZURE_OPENAI_KEY environment variable or pass it directly azure_endpoint use AZURE_OPENAI_ENDPOINT environment variable or pass it directly Anthropic #from langchain_anthropic import AnthropicChat llm = AnthropicChat( base_url=\u0026#34;https://\u0026lt;your-service\u0026gt;.services.ai.azure.com/anthropic/\u0026#34;, api_key=\u0026#34;\u0026lt;your-api-key\u0026gt;\u0026#34;, model=\u0026#34;claude-sonnet-4-5\u0026#34;, ) api_key use ANTHROPIC_API_KEY environment variable or pass it directly base_url use ANTHROPIC_API_URL then ANTHROPIC_BASE_URL environment variable or pass it AIChat #from langchain_azure_ai import AzureAIChatCompletionModel llm = AzureAIChatCompletionModel( azure_endpoint=\u0026#34;https://\u0026lt;your-service\u0026gt;.services.ai.azure.com/models\u0026#34;, azure_deployment=\u0026#34;gpt-5.2-chat\u0026#34;, api_version=\u0026#34;2024-05-01-preview\u0026#34; ) Need to set AZURE_AI_ENDPOINT to https://\u0026lt;your-service\u0026gt;.services.ai.azure.com/models API key use AZURE_AI_CREDENTIAL environment variable Actually, this method is not recommended, because it does not support some features and have poor compatibility to the langchain ecosystem.\n","date":"10 February 2026","permalink":"https://blog.saltfish.org/posts/ms-ai-foundry/","section":"Posts","summary":"\u003cp\u003eIn Microsoft\u0026rsquo;s AI Foundry, it might be a bit tricky to get it work with Langchain at first.\nWhat need to be specified is different model provider has different service endpoint and need to use different class to call the API.\u003c/p\u003e\n\u003ch2 id=\"azure-openai\" class=\"relative group\"\u003eAzure OpenAI \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#azure-openai\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003efrom\u003c/span\u003e langchain_openai \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e AzureChatOpenAI\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ellm \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e AzureChatOpenAI(\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    azure_deployment\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gpt-5.2-chat\u0026#34;\u003c/span\u003e,       \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    azure_endpoint\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;https://\u0026lt;your-service\u0026gt;.cognitiveservices.azure.com/\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    api_version\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;2024-05-01-preview\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    api_key\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026lt;your-api-key\u0026gt;\u0026#34;\u003c/span\u003e, \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    max_tokens\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e4096\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cul\u003e\n\u003cli\u003e\u003ccode\u003eapi_key\u003c/code\u003e use \u003ccode\u003eAZURE_OPENAI_KEY\u003c/code\u003e environment variable or pass it directly\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eazure_endpoint\u003c/code\u003e use \u003ccode\u003eAZURE_OPENAI_ENDPOINT\u003c/code\u003e environment variable or pass it directly\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"anthropic\" class=\"relative group\"\u003eAnthropic \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#anthropic\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003efrom\u003c/span\u003e langchain_anthropic \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e AnthropicChat\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ellm \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e AnthropicChat(\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    base_url\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;https://\u0026lt;your-service\u0026gt;.services.ai.azure.com/anthropic/\u0026#34;\u003c/span\u003e,       \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    api_key\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026lt;your-api-key\u0026gt;\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    model\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;claude-sonnet-4-5\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cul\u003e\n\u003cli\u003e\u003ccode\u003eapi_key\u003c/code\u003e use \u003ccode\u003eANTHROPIC_API_KEY\u003c/code\u003e environment variable or pass it directly\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003ebase_url\u003c/code\u003e use \u003ccode\u003eANTHROPIC_API_URL\u003c/code\u003e then \u003ccode\u003eANTHROPIC_BASE_URL\u003c/code\u003e environment variable or pass it\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"aichat\" class=\"relative group\"\u003eAIChat \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#aichat\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003efrom\u003c/span\u003e langchain_azure_ai \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e AzureAIChatCompletionModel\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ellm \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e AzureAIChatCompletionModel(\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    azure_endpoint\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;https://\u0026lt;your-service\u0026gt;.services.ai.azure.com/models\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    azure_deployment\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gpt-5.2-chat\u0026#34;\u003c/span\u003e,       \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    api_version\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;2024-05-01-preview\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cul\u003e\n\u003cli\u003eNeed to set \u003ccode\u003eAZURE_AI_ENDPOINT\u003c/code\u003e to \u003ccode\u003ehttps://\u0026lt;your-service\u0026gt;.services.ai.azure.com/models\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eAPI key use \u003ccode\u003eAZURE_AI_CREDENTIAL\u003c/code\u003e environment variable\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eActually, this method is not recommended, because it does not support some features and have poor compatibility to the langchain ecosystem.\u003c/p\u003e","title":"How to use Microsoft AI Foundry with Langchain"},{"content":"","date":null,"permalink":"https://blog.saltfish.org/tags/langchain/","section":"Tags","summary":"","title":"Langchain"},{"content":"","date":null,"permalink":"https://blog.saltfish.org/tags/microsoft/","section":"Tags","summary":"","title":"Microsoft"},{"content":"Start using hugo to write down some tehc blogs!\nI will try my best to keep it 100% human written!\n","date":"9 February 2026","permalink":"https://blog.saltfish.org/posts/my-first-post/","section":"Posts","summary":"\u003cp\u003eStart using hugo to write down some tehc blogs!\u003c/p\u003e\n\u003cp\u003eI will try my best to keep it 100% human written!\u003c/p\u003e","title":"My First Post"}]