I have a problem that I have been struggling with the check_pairs function for several days. Finally, I wrote a version of the code that in my opinion everything is correct and indeed in CS50 everything shows green except :( lock_pairs skips final pair if it creates cycle.
And I found this code:
// Recursive function to check if entry makes a circle
bool makes_circle(int cycle_start, int loser)
{
if (loser == cycle_start)
{
// If the current loser is the cycle start
// The entry makes a circle
return true;
}
for (int i = 0; i < candidate_count; i++)
{
if (locked[loser][i])
{
if (makes_circle(cycle_start, i))
{
// Forward progress through the circle
return true;
}
}
}
return false;
}
// Lock pairs into the candidate graph in order, without creating cycles
void lock_pairs()
{
for (int i = 0; i < pair_count; i++)
{
if (!makes_circle(pairs[i].winner, pairs[i].loser))
{
// Lock the pair unless it makes a circle
locked[pairs[i].winner][pairs[i].loser] = true;
}
}
}
Found it here: PSET 3 Tideman CS50 lock_pairs doesn't lock correctly
When I pasted it to tideman everything was green with check50 and I can't figure out why and how it differs from my function. I checked various combinations in debug50, drew a few pages and still don't understand what is the detail that makes this recursive function different from mine. Could someone explain to me step by step why this function is correct and mine is not?
My code:
void lock_pairs(void)
{
for (int i = 0; i < pair_count; i++)
{
bool cycle = false;
// check if the loser of the actual checking pair won with any other candidate
for (int j = 0; j < candidate_count && !cycle; j++)
{
if (locked[pairs[i].loser][j])
{
// if the loser beats any other candidate, check if this other candidate beats the winner of the actual checking pair
for (int k = 0; k < candidate_count && !cycle; k++)
{
if (locked[j][k])
{
if (k == pairs[i].winner)
{
cycle = true;
}
}
}
}
}
if (!cycle)
{
locked[pairs[i].winner][pairs[i].loser] = true;
}
}
return;
}
i did this implementation of lockin function and it seems to work perfectly in every case i could come up with but when i submit it cs50 says:
":( lock_pairs skips final pair if it creates cycle
lock_pairs did not correctly lock all non-cyclical pairs
:( lock_pairs skips middle pair if it creates a cycle
lock_pairs did not correctly lock all non-cyclical pairs"
thats my code
// Lock pairs into the candidate graph in order, without creating cycles
void lock_pairs(void)
{
// initialising an array with all the untargted candidates (no arrows pointing to them)
bool untargeted[candidate_count];
// initialising a variable to keep track of how many sources (untargted candidates)
int untar = 0;
// chacking all as untargeted
for (int i = 0; i < candidate_count; i++)
{
untargeted[i] = true;
untar++;
}
// iterating over all pairs
for (int i = 0; i < pair_count; i++)
{
// checking if the loser is the only untargeted candidate to prevent cycles
if (!(untargeted[pairs[i].loser] && untar == 1))
{
locked[pairs[i].winner][pairs[i].loser] = true;
// checking if loser was untargted to mark them as a targeted
if (untargeted[pairs[i].loser])
{
untar--;
untargeted[pairs[i].loser] = false;
}
}
}
return;
}
i already submitted and fine with losing the 2 marks i just need to know whats wrong with my code
I'm working on the CS50x's Tideman problem
(https://cs50.harvard.edu/x/2022/psets/3/tideman/).
All the tests passed except one:
:( lock_pairs skips final pair if it creates cycle
lock_pairs did not correctly lock all non-cyclical pairs
I guess the problem is in my custom function has_circle that checks if there will be a circle in a graph if an edge is placed.
I would really be grateful for your help. I wrote the rest of the code fairly quickly (for a begginer) but this bug is just....
This is the code:
bool has_circle(int loser, int winner)
{
// base case, break
if (winner == loser)
{
return true;
}
// iterate through all pairs to see if our loser is a winner against someone
for (int i = 0; i < pair_count; i++)
{
if (locked[loser][i] == true)
{
// check if primary winner is a loser against i, if edge can be placed
return has_circle(i, winner);
}
}
return false;
}
// Lock pairs into the candidate graph in order, without creating cycles
void lock_pairs(void)
{
//
for (int i = 0; i < pair_count; i++)
{
// check for circle
if (!has_circle(pairs[i].loser, pairs[i].winner))
{
locked[pairs[i].winner][pairs[i].loser] = true;
}
}
return;
}
This line
return has_circle(i, winner);
always returns a value, ending the the loop and the recursion, the first time locked[loser][i] is true, regardless of the actual result of has_circle.
Instead, you want the loop to be able to continue if has_circle returns false, but break the loop and return true when has_circle returns true.
bool has_circle(int loser, int winner)
{
// base case, break
if (winner == loser)
{
return true;
}
// iterate through all pairs to see if our loser is a winner against someone
for (int i = 0; i < pair_count; i++)
{
if (locked[loser][i] && has_circle(i, winner))
{
return true;
}
}
return false;
}
I'm stuck at the lock_pairs function of Tideman. I already wrote the code for it and it looks good. It also behaves as it should when running it through the debugger; however, every time I try it with check50 it gives me an error on "lock pair skips final pair if it creates a cycle".
In order to try it out, I've used examples where the final pair is the one which should be skipped and everything goes as planned when I watch the whole process step by step on the debugger, so I really don't know what I'm missing here.
Here's the explanation of the problem: https://cs50.harvard.edu/x/2022/psets/3/tideman/
void lock_pairs(void)
{
// TODO
// When no_cycle is 0, it indicates that there is no cycle and that locking the pair is safe
int no_cycle = 0;
for (int i = 0; i < pair_count; i++)
{
for (int j = 0; j < pair_count; j++)
{
// We check each pair, looking for a locked pair where the loser of the pair we're checking is the winner.
if (!locked[pairs[i].loser][pairs[j].loser])
{
no_cycle = 0;
continue;
}
else
{
// If the result is true, we check for a possible cycle using the cycle_check function
if (cycle_check(i, j) == false)
{
// If the result is false, we lock the pair and move on
no_cycle = 0;
break;
}
else
{
// If the result is true (indicating a cycle), we change the value of no_cycle to avoid locking the pair
no_cycle = 1;
break;
}
}
}
if (no_cycle == 0)
{
locked[pairs[i].winner][pairs[i].loser] = true;
}
else continue;
}
return;
}
bool cycle_check(i, j)
{
// We check if the loser of the locked pair is the same as our pair's winner or starting point (the end of the chain is also the begining)
if (pairs[j].loser == pairs[i].winner)
{
return true;
}
else
{
//If it isn't, we go through each locked pair, following the chain which could lead to the cycle
for (int h = 0; h < pair_count; h++)
{
if (locked[pairs[j].loser][pairs[h].loser])
{
// Once we find the next link in the chain, we start over
j = h;
if (cycle_check(i, j) == true)
{
// Once we find that there is indeed a cycle, we break recursion
return true;
}
}
}
// If we follow the chain but don't find a cycle, the function returns false and the pair is locked
return false;
}
}
Been stuck forever on this. Any help would be much appreciated.
Btw, english is not my primary language, so please excuse any grammar errors.
I am practicing to solve this problem, and have gotten 5 test cases passed but some test cases are failing I am not able to figure out what's the issue in my algorithm. Although I tried with some test data from failed test cases, most of them are coming correctly but I believe some are incorrect hence leading to my algorithm failure. So If someone can give an insight on the correct way to implement this algorithm that would be very helpful or where am I going wrong in my implementation.
My Algo:
1. Index for the move is at index '0' of string (say moving index)
2. Loop over the string starting with index '1' of string:
2.1. check if (moving index + leap) can outrun the array:
2.2. If not then, check whether the character is 1 or 0 :
2.2.1 Check for the number of '1's that are continuous, if they exceed the leap value then return false (as anyway we will not be able to jump).
2.2.2 If its 0, then check whether its a zero after continuous '1's.
If not so, continue moving forward one step at a time.
If so, first try to skip over those continuous '1's by checking whether (moving index + leap) is allowed or not as per the rule.
If not allowed, check in a while loop till what point we can move backwards one step at a time to get (moving index + leap) to satisfy.
If not possible, return false.
I don't know whether this is an efficient way to implement solution of this sort of problem, any other possible methods are much appreciated.
code:
import java.util.*;
public class Solution {
public static int leapStep(int index,int leap,int len,int[] game){
if(game[index+leap]==0){
index += leap;
}
return index;
}
public static boolean canWin(int leap, int[] game) {
int index = 0;
int len = game.length;
int consecutiveLength=0;
for(int i=1;i<len;){
if(index+leap>len-1){
return true;
}
if(game[i]==1){
consecutiveLength++;
if(consecutiveLength>=leap){
return false;
}
i++;
}else{
if(consecutiveLength==0){
index =i;
i++;
}else{
if(index+leap<=len-1){
int tryLeap = leapStep(index,leap,len,game);
if(index < tryLeap){
index = tryLeap;
tryLeap =0;
i = index+1;
}else if(index>0 && game[index-1]==0 ){
boolean notViable = false;
while(index>0){
if(game[index-1]!=0)
return false;
index -= 1;
i = index+1;
tryLeap = leapStep(index,leap,len,game);
if(index<tryLeap){
index = tryLeap;
i = index+1;
tryLeap=0;
notViable = false;
break;
}
else{
notViable = true;
}
}
if(notViable){
return false;
}
}else{
return false;
}
}
consecutiveLength=0;
}
}
}//closing for
return true;
}
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int q = scan.nextInt();
while (q-- > 0) {
int n = scan.nextInt();
int leap = scan.nextInt();
int[] game = new int[n];
for (int i = 0; i < n; i++) {
game[i] = scan.nextInt();
}
System.out.println( (canWin(leap, game)) ? "YES" : "NO" );
}
scan.close();
}
}
To me, a better approach is to solve this recursively as below (it passed all the tests):
public static boolean canWin(int[] array, int index, int leap) {
// the only case when we lose
if (index < 0 || array[index] > 0) {
return false;
}
// if you're standing in the last entry or (index + leap) >= array.length then win
if ((index >= array.length - 1) || ((index + leap) >= array.length)) {
return true;
}
// mark it as visited so that not to iterate over it again
array[index] = 1;
// check all 3 conditions then recursively again
return canWin(array, index + 1, leap) || canWin(array, index - 1, leap) || canWin(array, index + leap, leap);
}
In the input below several pairs of lines are shown. The first element of each pair stands for leap and the second one for an array.
Input:
3
0 0 0 0 0
5
0 0 0 1 1 1
3
0 0 1 1 1 0
1
0 1 0
Output:
true
true
false
false
Explanation:
Let's say your current position is index.
If it's negative or the array value is larger than 0 then the game is lost. If it's the last position or index + leap reaches at least the length of the array then the game is won by definition.
Otherwise, the only possible moves from here could be index - 1 or index + 1 or index + leap. So, you repeat step 1 for each of the latter indices and take OR of the result because finding a single path is enough. Don't forget to set a value of the cell to 1 because it doesn't make sense to visit it the second time - we don't want to repeat the same moves over and over again and crash.
Your pseudo-code seems fine, but there a few mistake in your code, that may be the cause of your trouble.
The least problematic first, if(index+leap<=len-1) inside your loop is useless, you can remove it without modify the behaviour of your algorithm. It is the case because you already checked it in the first line of the loop and entered an else keyword.
This one is about your variables index and i. Their meaning isn't clear to me after a few complete read, and they look like the same. It might cause you trouble because you use the variable index inside your call to leapStep, but index is often one step behind i. It's confusing.
I did not found an example where your code fails.
Here is my solution HackerRank accepted. It is an iterative one, close to yours. Its principle is simple: starting from position 0, as we increase step by step our position, keep track of the positions you have access to (in variable memoTab, I removed the dp name as it can be frightening): if we are on a position we already reached before, then we can go to +1 or +leap.
It would be enough if it wasn't allowed to backtrack and go the reverse direction. To deal with that, whenever we reach some 1s, I keep in memory the next 0. And if I encounter a position I can reach just after, I go back to that 0 and say I can go there.
Here is the code, first a little helper function that returns true if the game is finished. Given a game and an index it says if we can go to that index and write it to the memo.
public static boolean check(int[] game, boolean[] memo, int index){
if(index >= 0 && index < game.length){
if(game[index] != 1){
memo[index] = true;
}
}
return index >= game.length;
}
This is the solver function, it first reads the values, then starts looping.
public static void solveOne(){
int n = sc.nextInt();
int leap = sc.nextInt();
int[] game = new int[n];
for (int i = 0; i < n; i++) {
game[i] = sc.nextInt();
}
int index = 0;
boolean[] memoTab = new boolean[n];
for (int i = 0; i < n; i++) {
memoTab[i] = false;
}
memoTab[0] = true;
boolean rememberIndex0 = false;
boolean gotoIndex0 = false;
int index0 = 0;
boolean finished = false;
We are done with the initialization, let's loop:
while(index < game.length){
// we encounter the first 0 after some 1, keep it in memory !
if(rememberIndex0 && game[index] == 0){
index0 = index;
gotoIndex0 = true;
rememberIndex0 = false;
}
// this index is an index we reached before, we can continue from here
if(memoTab[index]){
// we previously said we need to go back to a lower position
if(gotoIndex0){
gotoIndex0 = false;
index = index0;
memoTab[index] = true;
continue;
}
// it's finished if either is true
finished = check(game, memoTab, index + 1)
|| check(game, memoTab, index + leap);
if(finished) break;
}
// if this position is a 1, then we will keep in memory the next 0
if(game[index] == 1){
rememberIndex0= true;
}
// don't forget incrementing
index += 1;
}
System.out.println(finished?"YES":"NO");
}